home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2005 October / PCWOCT05.iso / Software / FromTheMag / XAMPP 1.4.14 / xampp-win32-1.4.14-installer.exe / xampp / php / pear / DB / DataObject.php < prev    next >
PHP Script  |  2004-10-01  |  121KB  |  3,544 lines

  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PHP Version 4                                                        |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 1997-2003 The PHP Group                                |
  6. // +----------------------------------------------------------------------+
  7. // | This source file is subject to version 2.02 of the PHP license,      |
  8. // | that is bundled with this package in the file LICENSE, and is        |
  9. // | available at through the world-wide-web at                           |
  10. // | http://www.php.net/license/2_02.txt.                                 |
  11. // | If you did not receive a copy of the PHP license and are unable to   |
  12. // | obtain it through the world-wide-web, please send a note to          |
  13. // | license@php.net so we can mail you a copy immediately.               |
  14. // +----------------------------------------------------------------------+
  15. // | Author:  Alan Knowles <alan@akbkhome.com>
  16. // +----------------------------------------------------------------------+
  17. /**
  18.  * Object Based Database Query Builder and data store
  19.  *
  20.  * @package  DB_DataObject
  21.  * @category DB
  22.  *
  23.  * $Id: DataObject.php,v 1.294 2004/08/28 03:01:05 alan_k Exp $
  24.  */
  25.  
  26. /* =========================================================================== 
  27. *
  28. *    !!!!!!!!!!!!!               W A R N I N G                !!!!!!!!!!!
  29. *
  30. *  THIS MAY SEGFAULT PHP IF YOU ARE USING THE ZEND OPTIMIZER (to fix it, 
  31. *  just add "define('DB_DATAOBJECT_NO_OVERLOAD',true);" before you include 
  32. *  this file. reducing the optimization level may also solve the segfault.
  33. *  ===========================================================================
  34. */
  35.  
  36. /**
  37.  * The main "DB_DataObject" class is really a base class for your own tables classes
  38.  *
  39.  * // Set up the class by creating an ini file (refer to the manual for more details
  40.  * [DB_DataObject]
  41.  * database         = mysql:/username:password@host/database
  42.  * schema_location = /home/myapplication/database
  43.  * class_location  = /home/myapplication/DBTables/
  44.  * clase_prefix    = DBTables_
  45.  *
  46.  *
  47.  * //Start and initialize...................... - dont forget the &
  48.  * $config = parse_ini_file('example.ini',true);
  49.  * $options = &PEAR::getStaticProperty('DB_DataObject','options');
  50.  * $options = $config['DB_DataObject'];
  51.  *
  52.  * // example of a class (that does not use the 'auto generated tables data')
  53.  * class mytable extends DB_DataObject {
  54.  *     // mandatory - set the table
  55.  *     var $_database_dsn = "mysql://username:password@localhost/database";
  56.  *     var $__table = "mytable";
  57.  *     function table() {
  58.  *         return array(
  59.  *             'id' => 1, // integer or number
  60.  *             'name' => 2, // string
  61.  *        );
  62.  *     }
  63.  *     function keys() {
  64.  *         return array('id');
  65.  *     }
  66.  * }
  67.  *
  68.  * // use in the application
  69.  *
  70.  *
  71.  * Simple get one row
  72.  *
  73.  * $instance = new mytable;
  74.  * $instance->get("id",12);
  75.  * echo $instance->somedata;
  76.  *
  77.  *
  78.  * Get multiple rows
  79.  *
  80.  * $instance = new mytable;
  81.  * $instance->whereAdd("ID > 12");
  82.  * $instance->whereAdd("ID < 14");
  83.  * $instance->find();
  84.  * while ($instance->fetch()) {
  85.  *     echo $instance->somedata;
  86.  * }
  87.  
  88.  
  89. /**
  90.  * Needed classes
  91.  * - we use getStaticProperty from PEAR pretty extensively (cant remove it ATM)
  92.  */
  93.  
  94. require_once 'PEAR.php';
  95.  
  96. /**
  97.  * these are constants for the get_table array
  98.  * user to determine what type of escaping is required around the object vars.
  99.  */
  100. define('DB_DATAOBJECT_INT',  1);  // does not require ''
  101. define('DB_DATAOBJECT_STR',  2);  // requires ''
  102.  
  103. define('DB_DATAOBJECT_DATE', 4);  // is date #TODO
  104. define('DB_DATAOBJECT_TIME', 8);  // is time #TODO
  105. define('DB_DATAOBJECT_BOOL', 16); // is boolean #TODO
  106. define('DB_DATAOBJECT_TXT',  32); // is long text #TODO
  107. define('DB_DATAOBJECT_BLOB', 64); // is blob type
  108.  
  109.  
  110. define('DB_DATAOBJECT_NOTNULL', 128);           // not null col.
  111. define('DB_DATAOBJECT_MYSQLTIMESTAMP'   , 256);           // mysql timestamps (ignored by update/insert)
  112. /*
  113.  * Define this before you include DataObjects.php to  disable overload - if it segfaults due to Zend optimizer..
  114.  */
  115. //define('DB_DATAOBJECT_NO_OVERLOAD',true)  
  116.  
  117.  
  118. /**
  119.  * Theses are the standard error codes, most methods will fail silently - and return false
  120.  * to access the error message either use $table->_lastError
  121.  * or $last_error = PEAR::getStaticProperty('DB_DataObject','lastError');
  122.  * the code is $last_error->code, and the message is $last_error->message (a standard PEAR error)
  123.  */
  124.  
  125. define('DB_DATAOBJECT_ERROR_INVALIDARGS',   -1);  // wrong args to function
  126. define('DB_DATAOBJECT_ERROR_NODATA',        -2);  // no data available
  127. define('DB_DATAOBJECT_ERROR_INVALIDCONFIG', -3);  // something wrong with the config
  128. define('DB_DATAOBJECT_ERROR_NOCLASS',       -4);  // no class exists
  129. define('DB_DATAOBJECT_ERROR_NOTSUPPORTED'  ,-6);  // limit queries on unsuppored databases
  130. define('DB_DATAOBJECT_ERROR_INVALID_CALL'  ,-7);  // overlad getter/setter failure
  131.  
  132. /**
  133.  * Used in methods like delete() and count() to specify that the method should
  134.  * build the condition only out of the whereAdd's and not the object parameters.
  135.  */
  136. define('DB_DATAOBJECT_WHEREADD_ONLY', true);
  137.  
  138. /**
  139.  *
  140.  * storage for connection and result objects,
  141.  * it is done this way so that print_r()'ing the is smaller, and
  142.  * it reduces the memory size of the object.
  143.  * -- future versions may use $this->_connection = & PEAR object..
  144.  *   although will need speed tests to see how this affects it.
  145.  * - includes sub arrays
  146.  *   - connections = md5 sum mapp to pear db object
  147.  *   - results     = [id] => map to pear db object
  148.  *   - resultseq   = sequence id for results & results field
  149.  *   - resultfields = [id] => list of fields return from query (for use with toArray())
  150.  *   - ini         = mapping of database to ini file results
  151.  *   - links       = mapping of database to links file
  152.  *   - lasterror   = pear error objects for last error event.
  153.  *   - config      = aliased view of PEAR::getStaticPropery('DB_DataObject','options') * done for performance.
  154.  *   - array of loaded classes by autoload method - to stop it doing file access request over and over again!
  155.  */
  156. $GLOBALS['_DB_DATAOBJECT']['RESULTS']   = array();
  157. $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ'] = 1;
  158. $GLOBALS['_DB_DATAOBJECT']['RESULTFIELDS'] = array();
  159. $GLOBALS['_DB_DATAOBJECT']['CONNECTIONS'] = array();
  160. $GLOBALS['_DB_DATAOBJECT']['INI'] = array();
  161. $GLOBALS['_DB_DATAOBJECT']['LINKS'] = array();
  162. $GLOBALS['_DB_DATAOBJECT']['SEQUENCE'] = array();
  163. $GLOBALS['_DB_DATAOBJECT']['LASTERROR'] = null;
  164. $GLOBALS['_DB_DATAOBJECT']['CONFIG'] = array();
  165. $GLOBALS['_DB_DATAOBJECT']['CACHE'] = array();
  166. $GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = false;
  167. $GLOBALS['_DB_DATAOBJECT']['QUERYENDTIME'] = 0;
  168.  
  169.  
  170.  
  171. // this will be horrifically slow!!!!
  172. // NOTE: Overload SEGFAULTS ON PHP4 + Zend Optimizer (see define before..)
  173. // these two are BC/FC handlers for call in PHP4/5
  174.  
  175. if ( substr(phpversion(),0,1) == 5) {
  176.     class DB_DataObject_Overload {
  177.         function __call($method,$args) {
  178.             $return = null;
  179.             $this->_call($method,$args,$return);
  180.             return $return;
  181.         }
  182.     }
  183. } else {
  184.     if (!function_exists('clone')) {
  185.         eval('function clone($t) { return $t; }');
  186.     }
  187.     eval('
  188.         class DB_DataObject_Overload {
  189.             function __call($method,$args,&$return) {
  190.                 return $this->_call($method,$args,$return); 
  191.             }
  192.         }
  193.     ');
  194. }
  195.  
  196.     
  197.  
  198.  
  199.  
  200.  
  201.  /*
  202.  *
  203.  * @package  DB_DataObject
  204.  * @author   Alan Knowles <alan@akbkhome.com>
  205.  * @since    PHP 4.0
  206.  */
  207.  
  208. class DB_DataObject extends DB_DataObject_Overload
  209. {
  210.    /**
  211.     * The Version - use this to check feature changes
  212.     *
  213.     * @access   private
  214.     * @var      string
  215.     */
  216.     var $_DB_DataObject_version = "1.7.2";
  217.  
  218.     /**
  219.      * The Database table (used by table extends)
  220.      *
  221.      * @access  private
  222.      * @var     string
  223.      */
  224.     var $__table = '';  // database table
  225.  
  226.     /**
  227.      * The Number of rows returned from a query
  228.      *
  229.      * @access  public
  230.      * @var     int
  231.      */
  232.     var $N = 0;  // Number of rows returned from a query
  233.  
  234.  
  235.     /* ============================================================= */
  236.     /*                      Major Public Methods                     */
  237.     /* (designed to be optionally then called with parent::method()) */
  238.     /* ============================================================= */
  239.  
  240.  
  241.     /**
  242.      * Get a result using key, value.
  243.      *
  244.      * for example
  245.      * $object->get("ID",1234);
  246.      * Returns Number of rows located (usually 1) for success,
  247.      * and puts all the table columns into this classes variables
  248.      *
  249.      * see the fetch example on how to extend this.
  250.      *
  251.      * if no value is entered, it is assumed that $key is a value
  252.      * and get will then use the first key in keys()
  253.      * to obtain the key.
  254.      *
  255.      * @param   string  $k column
  256.      * @param   string  $v value
  257.      * @access  public
  258.      * @return  int     No. of rows
  259.      */
  260.     function get($k = null, $v = null)
  261.     {
  262.         global $_DB_DATAOBJECT;
  263.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  264.             DB_DataObject::_loadConfig();
  265.         }
  266.         $keys = array();
  267.         
  268.         if ($v === null) {
  269.             $v = $k;
  270.             $keys = $this->keys();
  271.             if (!$keys) {
  272.                 $this->raiseError("No Keys available for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  273.                 return false;
  274.             }
  275.             $k = $keys[0];
  276.         }
  277.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  278.             $this->debug("$k $v " .print_r($keys,true), "GET");
  279.         }
  280.         
  281.         if ($v === null) {
  282.             $this->raiseError("No Value specified for get", DB_DATAOBJECT_ERROR_INVALIDARGS);
  283.             return false;
  284.         }
  285.         $this->$k = $v;
  286.         return $this->find(1);
  287.     }
  288.  
  289.     /**
  290.      * An autoloading, caching static get method  using key, value (based on get)
  291.      *
  292.      * Usage:
  293.      * $object = DB_DataObject::staticGet("DbTable_mytable",12);
  294.      * or
  295.      * $object =  DB_DataObject::staticGet("DbTable_mytable","name","fred");
  296.      *
  297.      * or write it into your extended class:
  298.      * function &staticGet($k,$v=NULL) { return DB_DataObject::staticGet("This_Class",$k,$v);  }
  299.      *
  300.      * @param   string  $class class name
  301.      * @param   string  $k     column (or value if using keys)
  302.      * @param   string  $v     value (optional)
  303.      * @access  public
  304.      * @return  object
  305.      */
  306.     function &staticGet($class, $k, $v = null)
  307.     {
  308.         $lclass = strtolower($class);
  309.         global $_DB_DATAOBJECT;
  310.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  311.             DB_DataObject::_loadConfig();
  312.         }
  313.  
  314.         
  315.  
  316.         $key = "$k:$v";
  317.         if ($v === null) {
  318.             $key = $k;
  319.         }
  320.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  321.             DB_DataObject::debug("$class $key","STATIC GET - TRY CACHE");
  322.         }
  323.         if (!empty($_DB_DATAOBJECT['CACHE'][$lclass][$key])) {
  324.             return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
  325.         }
  326.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  327.             DB_DataObject::debug("$class $key","STATIC GET - NOT IN CACHE");
  328.         }
  329.  
  330.         $obj = DB_DataObject::factory(substr($class,strlen($_DB_DATAOBJECT['CONFIG']['class_prefix'])));
  331.         if (PEAR::isError($obj)) {
  332.             DB_DataObject::raiseError("could not autoload $class", DB_DATAOBJECT_ERROR_NOCLASS);
  333.             return false;
  334.         }
  335.         
  336.         if (!isset($_DB_DATAOBJECT['CACHE'][$lclass])) {
  337.             $_DB_DATAOBJECT['CACHE'][$lclass] = array();
  338.         }
  339.         if (!$obj->get($k,$v)) {
  340.             DB_DataObject::raiseError("No Data return from get $k $v", DB_DATAOBJECT_ERROR_NODATA);
  341.             return false;
  342.         }
  343.         $_DB_DATAOBJECT['CACHE'][$lclass][$key] = $obj;
  344.         return $_DB_DATAOBJECT['CACHE'][$lclass][$key];
  345.     }
  346.  
  347.     /**
  348.      * find results, either normal or crosstable
  349.      *
  350.      * for example
  351.      *
  352.      * $object = new mytable();
  353.      * $object->ID = 1;
  354.      * $object->find();
  355.      *
  356.      *
  357.      * will set $object->N to number of rows, and expects next command to fetch rows
  358.      * will return $object->N
  359.      *
  360.      * @param   boolean $n Fetch first result
  361.      * @access  public
  362.      * @return  int
  363.      */
  364.     function find($n = false)
  365.     {
  366.         global $_DB_DATAOBJECT;
  367.         if (!isset($this->_query)) {
  368.             $this->raiseError(
  369.                 "You cannot do two queries on the same object (copy it before finding)", 
  370.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  371.             return false;
  372.         }
  373.         
  374.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  375.             DB_DataObject::_loadConfig();
  376.         }
  377.  
  378.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  379.             $this->debug($n, "__find",1);
  380.         }
  381.         if (!$this->__table) {
  382.             echo "NO \$__table SPECIFIED in class definition";
  383.             exit;
  384.         }
  385.         $this->N = 0;
  386.         $query_before = $this->_query;
  387.         $this->_build_condition($this->table()) ;
  388.         
  389.         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  390.         $this->_connect();
  391.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  392.        
  393.  
  394.         $this->_query('SELECT ' .
  395.             $this->_query['data_select'] .
  396.             ' FROM ' . ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table) . " " .
  397.             $this->_join .
  398.             $this->_query['condition'] . ' '.
  399.             $this->_query['group_by']  . ' '.
  400.             $this->_query['having']    . ' '.
  401.             $this->_query['order_by']  . ' '.
  402.             
  403.             $this->_query['limit']); // is select
  404.         
  405.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  406.             $this->debug("CHECK autofetchd $n", "__find", 1);
  407.         }
  408.         // unset the 
  409.         
  410.         
  411.         if ($n && $this->N > 0 ) {
  412.              if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  413.                 $this->debug("ABOUT TO AUTOFETCH", "__find", 1);
  414.             }
  415.             $this->fetch() ;
  416.         }
  417.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  418.             $this->debug("DONE", "__find", 1);
  419.         }
  420.         $this->_query = $query_before;
  421.         return $this->N;
  422.     }
  423.  
  424.     /**
  425.      * fetches next row into this objects var's
  426.      *
  427.      * returns 1 on success 0 on failure
  428.      *
  429.      *
  430.      *
  431.      * Example
  432.      * $object = new mytable();
  433.      * $object->name = "fred";
  434.      * $object->find();
  435.      * $store = array();
  436.      * while ($object->fetch()) {
  437.      *   echo $this->ID;
  438.      *   $store[] = $object; // builds an array of object lines.
  439.      * }
  440.      *
  441.      * to add features to a fetch
  442.      * function fetch () {
  443.      *    $ret = parent::fetch();
  444.      *    $this->date_formated = date('dmY',$this->date);
  445.      *    return $ret;
  446.      * }
  447.      *
  448.      * @access  public
  449.      * @return  boolean on success
  450.      */
  451.     function fetch()
  452.     {
  453.  
  454.         global $_DB_DATAOBJECT;
  455.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  456.             DB_DataObject::_loadConfig();
  457.         }
  458.         if (empty($this->N)) {
  459.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  460.                 $this->debug("No data returned from FIND (eg. N is 0)","FETCH", 3);
  461.             }
  462.             return false;
  463.         }
  464.         
  465.         if (empty($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]) || 
  466.             !is_object($result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) 
  467.         {
  468.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  469.                 $this->debug('fetched on object after fetch completed (no results found)');
  470.             }
  471.             return false;
  472.         }
  473.         
  474.         
  475.         $array = $result->fetchRow(DB_FETCHMODE_ASSOC);
  476.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  477.             $this->debug(serialize($array),"FETCH");
  478.         }
  479.  
  480.         if ($array === null) {
  481.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  482.                 $t= explode(' ',microtime());
  483.             
  484.                 $this->debug("Last Data Fetch'ed after " . 
  485.                         ($t[0]+$t[1]- $_DB_DATAOBJECT['QUERYENDTIME']  ) . 
  486.                         " seconds",
  487.                     "FETCH", 1);
  488.             }
  489.             // reduce the memory usage a bit... (but leave the id in, so count() works ok on it)
  490.             unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
  491.             
  492.             // this is probably end of data!!
  493.             //DB_DataObject::raiseError("fetch: no data returned", DB_DATAOBJECT_ERROR_NODATA);
  494.             return false;
  495.         }
  496.         
  497.         if (!isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
  498.             // note: we dont declare this to keep the print_r size down.
  499.             $_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]= array_flip(array_keys($array));
  500.         }
  501.         
  502.         foreach($array as $k=>$v) {
  503.             $kk = str_replace(".", "_", $k);
  504.             $kk = str_replace(" ", "_", $kk);
  505.              if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  506.                 $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
  507.             }
  508.             $this->$kk = $array[$k];
  509.         }
  510.         
  511.         // set link flag
  512.         $this->_link_loaded=false;
  513.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  514.             $this->debug("{$this->__table} DONE", "fetchrow",2);
  515.         }
  516.         if (isset($this->_query) &&  empty($_DB_DATAOBJECT['CONFIG']['keep_query_after_fetch'])) {
  517.             unset($this->_query);
  518.         }
  519.         return true;
  520.     }
  521.  
  522.     /**
  523.      * Adds a condition to the WHERE statement, defaults to AND
  524.      *
  525.      * $object->whereAdd(); //reset or cleaer ewhwer
  526.      * $object->whereAdd("ID > 20");
  527.      * $object->whereAdd("age > 20","OR");
  528.      *
  529.      * @param    string  $cond  condition
  530.      * @param    string  $logic optional logic "OR" (defaults to "AND")
  531.      * @access   public
  532.      * @return   string|PEAR::Error - previous condition or Error when invalid args found
  533.      */
  534.     function whereAdd($cond = false, $logic = 'AND')
  535.     {
  536.         if (!isset($this->_query)) {
  537.             return $this->raiseError(
  538.                 "You cannot do two queries on the same object (clone it before finding)", 
  539.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  540.         }
  541.         
  542.         if ($cond === false) {
  543.             $r = $this->_query['condition'];
  544.             $this->_query['condition'] = '';
  545.             return $r;
  546.         }
  547.         // check input...= 0 or '   ' == error!
  548.         if (!trim($cond)) {
  549.             return $this->raiseError("WhereAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  550.         }
  551.         $r = $this->_query['condition'];
  552.         if ($this->_query['condition']) {
  553.             $this->_query['condition'] .= " {$logic} {$cond}";
  554.             return $r;
  555.         }
  556.         $this->_query['condition'] = " WHERE {$cond}";
  557.         return $r;
  558.     }
  559.  
  560.     /**
  561.      * Adds a order by condition
  562.      *
  563.      * $object->orderBy(); //clears order by
  564.      * $object->orderBy("ID");
  565.      * $object->orderBy("ID,age");
  566.      *
  567.      * @param  string $order  Order
  568.      * @access public
  569.      * @return none|PEAR::Error - invalid args only
  570.      */
  571.     function orderBy($order = false)
  572.     {
  573.         if (!isset($this->_query)) {
  574.             $this->raiseError(
  575.                 "You cannot do two queries on the same object (copy it before finding)", 
  576.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  577.             return false;
  578.         }
  579.         if ($order === false) {
  580.             $this->_query['order_by'] = '';
  581.             return;
  582.         }
  583.         // check input...= 0 or '    ' == error!
  584.         if (!trim($order)) {
  585.             return $this->raiseError("orderBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  586.         }
  587.         
  588.         if (!$this->_query['order_by']) {
  589.             $this->_query['order_by'] = " ORDER BY {$order} ";
  590.             return;
  591.         }
  592.         $this->_query['order_by'] .= " , {$order}";
  593.     }
  594.  
  595.     /**
  596.      * Adds a group by condition
  597.      *
  598.      * $object->groupBy(); //reset the grouping
  599.      * $object->groupBy("ID DESC");
  600.      * $object->groupBy("ID,age");
  601.      *
  602.      * @param  string  $group  Grouping
  603.      * @access public
  604.      * @return none|PEAR::Error - invalid args only
  605.      */
  606.     function groupBy($group = false)
  607.     {
  608.         if (!isset($this->_query)) {
  609.             $this->raiseError(
  610.                 "You cannot do two queries on the same object (copy it before finding)", 
  611.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  612.             return false;
  613.         }
  614.         if ($group === false) {
  615.             $this->_query['group_by'] = '';
  616.             return;
  617.         }
  618.         // check input...= 0 or '    ' == error!
  619.         if (!trim($group)) {
  620.             return $this->raiseError("groupBy: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  621.         }
  622.         
  623.         
  624.         if (!$this->_query['group_by']) {
  625.             $this->_query['group_by'] = " GROUP BY {$group} ";
  626.             return;
  627.         }
  628.         $this->_query['group_by'] .= " , {$group}";
  629.     }
  630.  
  631.     /**
  632.      * Adds a having clause
  633.      *
  634.      * $object->having(); //reset the grouping
  635.      * $object->having("sum(value) > 0 ");
  636.      *
  637.      * @param  string  $having  condition
  638.      * @access public
  639.      * @return none|PEAR::Error - invalid args only
  640.      */
  641.     function having($having = false)
  642.     {
  643.         if (!isset($this->_query)) {
  644.             $this->raiseError(
  645.                 "You cannot do two queries on the same object (copy it before finding)", 
  646.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  647.             return false;
  648.         }
  649.         if ($having === false) {
  650.             $this->_query['having'] = '';
  651.             return;
  652.         }
  653.         // check input...= 0 or '    ' == error!
  654.         if (!trim($having)) {
  655.             return $this->raiseError("Having: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  656.         }
  657.         
  658.         
  659.         if (!$this->_query['having']) {
  660.             $this->_query['having'] = " HAVING {$having} ";
  661.             return;
  662.         }
  663.         $this->_query['having'] .= " , {$having}";
  664.     }
  665.  
  666.     /**
  667.      * Sets the Limit
  668.      *
  669.      * $boject->limit(); // clear limit
  670.      * $object->limit(12);
  671.      * $object->limit(12,10);
  672.      *
  673.      * Note this will emit an error on databases other than mysql/postgress
  674.      * as there is no 'clean way' to implement it. - you should consider refering to
  675.      * your database manual to decide how you want to implement it.
  676.      *
  677.      * @param  string $a  limit start (or number), or blank to reset
  678.      * @param  string $b  number
  679.      * @access public
  680.      * @return none|PEAR::Error - invalid args only
  681.      */
  682.     function limit($a = null, $b = null)
  683.     {
  684.         if (!isset($this->_query)) {
  685.             $this->raiseError(
  686.                 "You cannot do two queries on the same object (copy it before finding)", 
  687.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  688.             return false;
  689.         }
  690.         
  691.         if ($a === null) {
  692.            $this->_query['limit'] = '';
  693.            return;
  694.         }
  695.         // check input...= 0 or '    ' == error!
  696.         if ((!is_int($a) && ((string)((int)$a) !== (string)$a)) 
  697.             || (($b !== null) && (!is_int($b) && ((string)((int)$b) !== (string)$b)))) {
  698.             return $this->raiseError("limit: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  699.         }
  700.         global $_DB_DATAOBJECT;
  701.         $this->_connect();
  702.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  703.        
  704.         if (($DB->features['limit'] == 'alter') && ($DB->phptype != 'oci8')) {
  705.             if ($b === null) {
  706.                $this->_query['limit'] = " LIMIT $a";
  707.                return;
  708.             }
  709.              
  710.             $this->_query['limit'] = $DB->modifyLimitQuery('',$a,$b);
  711.             
  712.         } else {
  713.             $this->raiseError(
  714.                 "DB_DataObjects only supports limit queries on some databases,\n".
  715.                 "Check with pear bugs for the package, or the dataobjects manual.\n",
  716.                 "or Refer to your Database manual to find out how to do limit queries manually.\n",
  717.                 DB_DATAOBJECT_ERROR_NOTSUPPORTED, PEAR_ERROR_DIE);
  718.         }
  719.     }
  720.  
  721.     /**
  722.      * Adds a select columns
  723.      *
  724.      * $object->selectAdd(); // resets select to nothing!
  725.      * $object->selectAdd("*"); // default select
  726.      * $object->selectAdd("unixtime(DATE) as udate");
  727.      * $object->selectAdd("DATE");
  728.      *
  729.      * to prepend distict:
  730.      * $object->selectAdd('distinct ' . $object->selectAdd());
  731.      *
  732.      * @param  string  $k
  733.      * @access public
  734.      * @return mixed null or old string if you reset it.
  735.      */
  736.     function selectAdd($k = null)
  737.     {
  738.         if (!isset($this->_query)) {
  739.             $this->raiseError(
  740.                 "You cannot do two queries on the same object (copy it before finding)", 
  741.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  742.             return false;
  743.         }
  744.         if ($k === null) {
  745.             $old = $this->_query['data_select'];
  746.             $this->_query['data_select'] = '';
  747.             return $old;
  748.         }
  749.         
  750.         // check input...= 0 or '    ' == error!
  751.         if (!trim($k)) {
  752.             return $this->raiseError("selectAdd: No Valid Arguments", DB_DATAOBJECT_ERROR_INVALIDARGS);
  753.         }
  754.         
  755.         if ($this->_query['data_select']) {
  756.             $this->_query['data_select'] .= ', ';
  757.         }
  758.         $this->_query['data_select'] .= " $k ";
  759.     }
  760.     /**
  761.      * Adds multiple Columns or objects to select with formating.
  762.      *
  763.      * $object->selectAs(null); // adds "table.colnameA as colnameA,table.colnameB as colnameB,......"
  764.      *                      // note with null it will also clear the '*' default select
  765.      * $object->selectAs(array('a','b'),'%s_x'); // adds "a as a_x, b as b_x"
  766.      * $object->selectAs(array('a','b'),'ddd_%s','ccc'); // adds "ccc.a as ddd_a, ccc.b as ddd_b"
  767.      * $object->selectAdd($object,'prefix_%s'); // calls $object->get_table and adds it all as
  768.      *                  objectTableName.colnameA as prefix_colnameA
  769.      *
  770.      * @param  array|object|null the array or object to take column names from.
  771.      * @param  string           format in sprintf format (use %s for the colname)
  772.      * @param  string           table name eg. if you have joinAdd'd or send $from as an array.
  773.      * @access public
  774.      * @return void
  775.      */
  776.     function selectAs($from = null,$format = '%s',$tableName=false)
  777.     {
  778.         global $_DB_DATAOBJECT;
  779.         
  780.         if (!isset($this->_query)) {
  781.             $this->raiseError(
  782.                 "You cannot do two queries on the same object (copy it before finding)", 
  783.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  784.             return false;
  785.         }
  786.         
  787.         if ($from === null) {
  788.             // blank the '*' 
  789.             $this->selectAdd();
  790.             $from = $this;
  791.         }
  792.         
  793.         
  794.         $table = $this->__table;
  795.         if (is_object($from)) {
  796.             $table = $from->__table;
  797.             $from = array_keys($from->table());
  798.         }
  799.         
  800.         if ($tableName !== false) {
  801.             $table = $tableName;
  802.         }
  803.         $s = '%s';
  804.         if (!empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers'])) {
  805.             $this->_connect();
  806.             $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  807.             $s      = $DB->quoteIdentifier($s);
  808.         }
  809.         foreach ($from as $k) {
  810.             $this->selectAdd(sprintf("{$s}.{$s} as {$format}",$table,$k,$k));
  811.         }
  812.         $this->_query['data_select'] .= "\n";
  813.     }
  814.     /**
  815.      * Insert the current objects variables into the database
  816.      *
  817.      * Returns the ID of the inserted element - mysql specific = fixme?
  818.      *
  819.      * for example
  820.      *
  821.      * Designed to be extended
  822.      *
  823.      * $object = new mytable();
  824.      * $object->name = "fred";
  825.      * echo $object->insert();
  826.      *
  827.      * @access public
  828.      * @return mixed True on success, false on failure, 0 on no data affected
  829.      */
  830.     function insert()
  831.     {
  832.         global $_DB_DATAOBJECT;
  833.         
  834.         // we need to write to the connection (For nextid) - so us the real
  835.         // one not, a copyied on (as ret-by-ref fails with overload!)
  836.         
  837.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  838.             $this->_connect();
  839.         }
  840.         
  841.         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  842.         
  843.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  844.          
  845.         $items =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?   
  846.             $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
  847.             
  848.         if (!$items) {
  849.             $this->raiseError("insert:No table definition for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  850.             return false;
  851.         }
  852.         $options= &$_DB_DATAOBJECT['CONFIG'];
  853.  
  854.  
  855.         $datasaved = 1;
  856.         $leftq     = '';
  857.         $rightq    = '';
  858.      
  859.         $seqKeys   = isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table]) ?
  860.                         $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] : 
  861.                         $this->sequenceKey();
  862.         
  863.         $key       = isset($seqKeys[0]) ? $seqKeys[0] : false;
  864.         $useNative = isset($seqKeys[1]) ? $seqKeys[1] : false;
  865.         $seq       = isset($seqKeys[2]) ? $seqKeys[2] : false;
  866.         
  867.         $dbtype    = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["phptype"];
  868.         
  869.          
  870.         // nativeSequences or Sequences..     
  871.  
  872.         // big check for using sequences
  873.         
  874.         if (($key !== false) && !$useNative) { 
  875.         
  876.             if (!$seq) {
  877.                 $this->$key = $DB->nextId($this->__table);
  878.             } else {
  879.                 $f = $DB->getOption('seqname_format');
  880.                 $DB->setOption('seqname_format','%s');
  881.                 $this->$key =  $DB->nextId($seq);
  882.                 $DB->setOption('seqname_format',$f);
  883.             }
  884.         }
  885.  
  886.  
  887.  
  888.         foreach($items as $k => $v) {
  889.             
  890.             // if we are using autoincrement - skip the column...
  891.             if ($key && ($k == $key) && $useNative) {
  892.                 continue;
  893.             }
  894.         
  895.             
  896.             if (!isset($this->$k)) {
  897.                 continue;
  898.             }
  899.             // dont insert data into mysql timestamps 
  900.             // use query() if you really want to do this!!!!
  901.             if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
  902.                 continue;
  903.             }
  904.             
  905.             if ($leftq) {
  906.                 $leftq  .= ', ';
  907.                 $rightq .= ', ';
  908.             }
  909.             
  910.             $leftq .= ($quoteIdentifiers ? ($DB->quoteIdentifier($k) . ' ')  : "$k ");
  911.             
  912.             if (is_a($this->$k,'db_dataobject_cast')) {
  913.                 $value = $this->$k->toString($v,$dbtype);
  914.                 if (PEAR::isError($value)) {
  915.                     $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
  916.                     return false;
  917.                 }
  918.                 $rightq .=  $value;
  919.                 continue;
  920.             }
  921.             
  922.  
  923.             if ((strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
  924.                 $rightq .= " NULL ";
  925.                 continue;
  926.             }
  927.             // DATE is empty... on a col. that can be null.. 
  928.             // note: this may be usefull for time as well..
  929.             if (!$this->$k && 
  930.                     (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) && 
  931.                     !($v & DB_DATAOBJECT_NOTNULL)) {
  932.                     
  933.                 $rightq .= " NULL ";
  934.                 continue;
  935.             }
  936.             
  937.             if ($v & DB_DATAOBJECT_STR) {
  938.                 $rightq .= $DB->quote($this->$k) . " ";
  939.                 continue;
  940.             }
  941.             if (is_numeric($this->$k)) {
  942.                 $rightq .=" {$this->$k} ";
  943.                 continue;
  944.             }
  945.             // at present we only cast to integers
  946.             // - V2 may store additional data about float/int
  947.             $rightq .= ' ' . intval($this->$k) . ' ';
  948.  
  949.         }
  950.         
  951.         
  952.         if ($leftq || $useNative) {
  953.             $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table)    : $this->__table);
  954.             
  955.             $r = $this->_query("INSERT INTO {$table} ($leftq) VALUES ($rightq) ");
  956.  
  957.             
  958.             
  959.             if (PEAR::isError($r)) {
  960.                 $this->raiseError($r);
  961.                 return false;
  962.             }
  963.             
  964.             if ($r < 1) {
  965.                 return 0;
  966.             }
  967.             
  968.             
  969.             // now do we have an integer key!
  970.             
  971.             if ($key && $useNative) {
  972.                 switch ($dbtype) {
  973.                     case 'mysql':
  974.                         $this->$key = mysql_insert_id(
  975.                             $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->connection
  976.                         );
  977.                         break;
  978.                     case 'mssql':
  979.                         // note this is not really thread safe - you should wrapp it with 
  980.                         // transactions = eg.
  981.                         // $db->query('BEGIN');
  982.                         // $db->insert();
  983.                         // $db->query('COMMIT');
  984.                         
  985.                         $mssql_key = $DB->getOne("SELECT @@IDENTITY");
  986.                         if (PEAR::isError($mssql_key)) {
  987.                             $this->raiseError($r);
  988.                             return false;
  989.                         }
  990.                         $this->$key = $mssql_key;
  991.                         break; 
  992.                         
  993.                     case 'pgsql':
  994.                         if (!$seq) {
  995.                             $seq = $DB->getSequenceName($this->__table );
  996.                         }
  997.                         $pgsql_key = $DB->getOne("SELECT last_value FROM ".$seq);
  998.                         if (PEAR::isError($pgsql_key)) {
  999.                             $this->raiseError($r);
  1000.                             return false;
  1001.                         }
  1002.                         $this->$key = $pgsql_key;
  1003.                         break;
  1004.                 }
  1005.                         
  1006.             }
  1007.  
  1008.             if (isset($_DB_DATAOBJECT['CACHE'][strtolower(get_class($this))])) {
  1009.                 $this->_clear_cache();
  1010.             }
  1011.             if ($key) {
  1012.                 return $this->$key;
  1013.             }
  1014.             return true;
  1015.         }
  1016.         $this->raiseError("insert: No Data specifed for query", DB_DATAOBJECT_ERROR_NODATA);
  1017.         return false;
  1018.     }
  1019.  
  1020.     /**
  1021.      * Updates  current objects variables into the database
  1022.      * uses the keys() to decide how to update
  1023.      * Returns the  true on success
  1024.      *
  1025.      * for example
  1026.      *
  1027.      * $object = new mytable();
  1028.      * $object->get("ID",234);
  1029.      * $object->email="testing@test.com";
  1030.      * if(!$object->update())
  1031.      *   echo "UPDATE FAILED";
  1032.      *
  1033.      * to only update changed items :
  1034.      * $dataobject->get(132);
  1035.      * $original = $dataobject; // clone/copy it..
  1036.      * $dataobject->setFrom($_POST);
  1037.      * if ($dataobject->validate()) {
  1038.      *    $dataobject->update($original);
  1039.      * } // otherwise an error...
  1040.      *
  1041.      *
  1042.      * @param object dataobject (optional) - used to only update changed items.
  1043.      * @access public
  1044.      * @return  int rows affected or false on failure
  1045.      */
  1046.     function update($dataObject = false)
  1047.     {
  1048.         global $_DB_DATAOBJECT;
  1049.         // connect will load the config!
  1050.         $this->_connect();
  1051.         
  1052.         
  1053.         $original_query = isset($this->_query) ? $this->_query : null;
  1054.         
  1055.         $items =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?   
  1056.             $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
  1057.         
  1058.         
  1059.         $keys  = $this->keys();
  1060.         
  1061.          
  1062.         if (!$items) {
  1063.             $this->raiseError("update:No table definition for {$this->__table}", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  1064.             return false;
  1065.         }
  1066.         $datasaved = 1;
  1067.         $settings  = '';
  1068.         $this->_connect();
  1069.         
  1070.         $DB            = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1071.         $dbtype        = $DB->dsn["phptype"];
  1072.         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1073.         
  1074.         foreach($items as $k => $v) {
  1075.             if (!isset($this->$k)) {
  1076.                 continue;
  1077.             }
  1078.             // ignore stuff thats 
  1079.           
  1080.             // dont write things that havent changed..
  1081.             if (($dataObject !== false) && isset($dataObject->$k) && ($dataObject->$k == $this->$k)) {
  1082.                 continue;
  1083.             }
  1084.             
  1085.             // beta testing.. - dont write keys to left.!!!
  1086.             if (in_array($k,$keys)) {
  1087.                 continue;
  1088.             }
  1089.             
  1090.              // dont insert data into mysql timestamps 
  1091.             // use query() if you really want to do this!!!!
  1092.             if ($v & DB_DATAOBJECT_MYSQLTIMESTAMP) {
  1093.                 continue;
  1094.             }
  1095.             
  1096.             
  1097.             if ($settings)  {
  1098.                 $settings .= ', ';
  1099.             }
  1100.             
  1101.             $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
  1102.             
  1103.             if (is_a($this->$k,'db_dataobject_cast')) {
  1104.                 $value = $this->$k->toString($v,$dbtype);
  1105.                 if (PEAR::isError($value)) {
  1106.                     $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
  1107.                     return false;
  1108.                 }
  1109.                 $settings .= "$kSql = $value ";
  1110.                 continue;
  1111.             }
  1112.             
  1113.             // special values ... at least null is handled...
  1114.             if ((strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
  1115.                 $settings .= "$kSql = NULL ";
  1116.                 continue;
  1117.             }
  1118.             // DATE is empty... on a col. that can be null.. 
  1119.             // note: this may be usefull for time as well..
  1120.             if (!$this->$k && 
  1121.                     (($v & DB_DATAOBJECT_DATE) || ($v & DB_DATAOBJECT_TIME)) && 
  1122.                     !($v & DB_DATAOBJECT_NOTNULL)) {
  1123.                     
  1124.                 $settings .= "$kSql = NULL ";
  1125.                 continue;
  1126.             }
  1127.             
  1128.  
  1129.             if ($v & DB_DATAOBJECT_STR) {
  1130.                 $settings .= "$kSql = ". $DB->quote($this->$k) . ' ';
  1131.                 continue;
  1132.             }
  1133.             if (is_numeric($this->$k)) {
  1134.                 $settings .= "$kSql = {$this->$k} ";
  1135.                 continue;
  1136.             }
  1137.             // at present we only cast to integers
  1138.             // - V2 may store additional data about float/int
  1139.             $settings .= "$kSql = " . intval($this->$k) . ' ';
  1140.         }
  1141.  
  1142.         
  1143.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1144.             $this->debug("got keys as ".serialize($keys),3);
  1145.         }
  1146.         
  1147.         $this->_build_condition($items,$keys);
  1148.         
  1149.         //  echo " $settings, $this->condition ";
  1150.         if ($settings && isset($this->_query) && $this->_query['condition']) {
  1151.             
  1152.             $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
  1153.         
  1154.             $r = $this->_query("UPDATE  {$table}  SET {$settings} {$this->_query['condition']} ");
  1155.             
  1156.             // restore original query conditions.
  1157.             $this->_query = $original_query;
  1158.             
  1159.             if (PEAR::isError($r)) {
  1160.                 $this->raiseError($r);
  1161.                 return false;
  1162.             }
  1163.             if ($r < 1) {
  1164.                 return 0;
  1165.             }
  1166.  
  1167.             $this->_clear_cache();
  1168.             return $r;
  1169.         }
  1170.         // restore original query conditions.
  1171.         $this->_query = $original_query;
  1172.         
  1173.         // if you manually specified a dataobject, and there where no changes - then it's ok..
  1174.         if ($dataObject !== false) {
  1175.             return false;
  1176.         }
  1177.         
  1178.         $this->raiseError(
  1179.             "update: No Data specifed for query $settings , {$this->_query['condition']}", 
  1180.             DB_DATAOBJECT_ERROR_NODATA);
  1181.         return false;
  1182.     }
  1183.  
  1184.     /**
  1185.      * Deletes items from table which match current objects variables
  1186.      *
  1187.      * Returns the true on success
  1188.      *
  1189.      * for example
  1190.      *
  1191.      * Designed to be extended
  1192.      *
  1193.      * $object = new mytable();
  1194.      * $object->ID=123;
  1195.      * echo $object->delete(); // builds a conditon
  1196.      *
  1197.      * $object = new mytable();
  1198.      * $object->whereAdd('age > 12');
  1199.      * $object->limit(1);
  1200.      * $object->orderBy('age DESC');
  1201.      * $object->delete(true); // dont use object vars, use the conditions, limit and order.
  1202.      *
  1203.      * @param bool $useWhere (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
  1204.      *             we will build the condition only using the whereAdd's.  Default is to
  1205.      *             build the condition only using the object parameters.
  1206.      *
  1207.      * @access public
  1208.      * @return mixed True on success, false on failure, 0 on no data affected
  1209.      */
  1210.     function delete($useWhere = false)
  1211.     {
  1212.         global $_DB_DATAOBJECT;
  1213.         // connect will load the config!
  1214.         $this->_connect();
  1215.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1216.         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1217.         
  1218.         $extra_cond = ' ' . (isset($this->_query['order_by']) ? $this->_query['order_by'] : '') . 
  1219.                       ' ' . (isset($this->_query['limit']) ? $this->_query['limit'] : '');
  1220.         
  1221.         if (!$useWhere) {
  1222.  
  1223.             $keys = $this->keys();
  1224.             $this->_query = array(); // as it's probably unset!
  1225.             $this->_query['condition'] = ''; // default behaviour not to use where condition
  1226.             $this->_build_condition($this->table(),$keys);
  1227.             // if primary keys are not set then use data from rest of object.
  1228.             if (!$this->_query['condition']) {
  1229.                 $this->_build_condition($this->table(),array(),$keys);
  1230.             }
  1231.             $extra_cond = '';
  1232.         } 
  1233.             
  1234.  
  1235.         // don't delete without a condition
  1236.         if (isset($this->_query) && $this->_query['condition']) {
  1237.         
  1238.             $table = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
  1239.         
  1240.             $r = $this->_query("DELETE FROM {$table} {$this->_query['condition']}{$extra_cond}");
  1241.             
  1242.             
  1243.             if (PEAR::isError($r)) {
  1244.                 $this->raiseError($r);
  1245.                 return false;
  1246.             }
  1247.             if ($r < 1) {
  1248.                 return 0;
  1249.             }
  1250.             $this->_clear_cache();
  1251.             return $r;
  1252.         } else {
  1253.             $this->raiseError("delete: No condition specifed for query", DB_DATAOBJECT_ERROR_NODATA);
  1254.             return false;
  1255.         }
  1256.     }
  1257.  
  1258.     /**
  1259.      * fetches a specific row into this object variables
  1260.      *
  1261.      * Not recommended - better to use fetch()
  1262.      *
  1263.      * Returens true on success
  1264.      *
  1265.      * @param  int   $row  row
  1266.      * @access public
  1267.      * @return boolean true on success
  1268.      */
  1269.     function fetchRow($row = null)
  1270.     {
  1271.         global $_DB_DATAOBJECT;
  1272.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  1273.             $this->_loadConfig();
  1274.         }
  1275.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1276.             $this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3);
  1277.         }
  1278.         if (!$this->__table) {
  1279.             $this->raiseError("fetchrow: No table", DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  1280.             return false;
  1281.         }
  1282.         if ($row === null) {
  1283.             $this->raiseError("fetchrow: No row specified", DB_DATAOBJECT_ERROR_INVALIDARGS);
  1284.             return false;
  1285.         }
  1286.         if (!$this->N) {
  1287.             $this->raiseError("fetchrow: No results avaiable", DB_DATAOBJECT_ERROR_NODATA);
  1288.             return false;
  1289.         }
  1290.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1291.             $this->debug("{$this->__table} $row of {$this->N}", "fetchrow",3);
  1292.         }
  1293.  
  1294.  
  1295.         $result = &$_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
  1296.         $array  = $result->fetchrow(DB_FETCHMODE_ASSOC,$row);
  1297.         if (!is_array($array)) {
  1298.             $this->raiseError("fetchrow: No results available", DB_DATAOBJECT_ERROR_NODATA);
  1299.             return false;
  1300.         }
  1301.  
  1302.         foreach($array as $k => $v) {
  1303.             $kk = str_replace(".", "_", $k);
  1304.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1305.                 $this->debug("$kk = ". $array[$k], "fetchrow LINE", 3);
  1306.             }
  1307.             $this->$kk = $array[$k];
  1308.         }
  1309.  
  1310.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1311.             $this->debug("{$this->__table} DONE", "fetchrow", 3);
  1312.         }
  1313.         return true;
  1314.     }
  1315.  
  1316.     /**
  1317.      * Find the number of results from a simple query
  1318.      *
  1319.      * for example
  1320.      *
  1321.      * $object = new mytable();
  1322.      * $object->name = "fred";
  1323.      * echo $object->count();
  1324.      * echo $object->count(true);  // dont use object vars.
  1325.      * echo $object->count('distinct mycol'); 
  1326.      * echo $object->count('distinct mycol',true); // dont use object vars.
  1327.      *
  1328.      *
  1329.      * @param bool|string  (optional)
  1330.      *                  (true|false = see below not on whereAddonly)
  1331.      *                  (string)
  1332.      *                  $countWhat (optional) normally it counts primary keys - you can use 
  1333.      *                  this to do things like $do->count('distinct mycol');
  1334.      * @param bool      $whereAddOnly (optional) If DB_DATAOBJECT_WHEREADD_ONLY is passed in then
  1335.      *                  we will build the condition only using the whereAdd's.  Default is to
  1336.      *                  build the condition using the object parameters as well.
  1337.      *                  
  1338.      * @access public
  1339.      * @return int
  1340.      */
  1341.     function count($countWhat = false,$whereAddOnly = false)
  1342.     {
  1343.         global $_DB_DATAOBJECT;
  1344.         
  1345.         if (is_bool($countWhat)) {
  1346.             $whereAddOnly = $countWhat;
  1347.         }
  1348.         
  1349.         $t = clone($this);
  1350.         
  1351.         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  1352.         
  1353.         $items   = $t->table();
  1354.         if (!isset($t->_query)) {
  1355.             $this->raiseError(
  1356.                 "You cannot do run count after you have run fetch()", 
  1357.                 DB_DATAOBJECT_ERROR_INVALIDARGS);
  1358.             return false;
  1359.         }
  1360.         $this->_connect();
  1361.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1362.        
  1363.  
  1364.         if (!$whereAddOnly && $items)  {
  1365.             $t->_build_condition($items);
  1366.         }
  1367.         $keys = $this->keys();
  1368.  
  1369.         if (!$keys[0] && !is_string($countWhat)) {
  1370.             $this->raiseError(
  1371.                 "You cannot do run count without keys - use \$do->keys('id');", 
  1372.                 DB_DATAOBJECT_ERROR_INVALIDARGS,PEAR_ERROR_DIE);
  1373.             return false;
  1374.             
  1375.         }
  1376.         $table   = ($quoteIdentifiers ? $DB->quoteIdentifier($this->__table) : $this->__table);
  1377.         if (!is_string($countWhat)) {
  1378.             $key_col = ($quoteIdentifiers ? $DB->quoteIdentifier($keys[0]) : $keys[0]);
  1379.         }
  1380.         
  1381.         $as      = ($quoteIdentifiers ? $DB->quoteIdentifier('DATAOBJECT_NUM') : 'DATAOBJECT_NUM');
  1382.         
  1383.         $countWhat = is_string($countWhat) ? $countWhat : "{$table}.{$key_col}";
  1384.         
  1385.         $r = $t->_query(
  1386.             "SELECT count({$countWhat}) as $as
  1387.                 FROM $table {$t->_join} {$t->_query['condition']}");
  1388.         if (PEAR::isError($r)) {
  1389.             return false;
  1390.         }
  1391.          
  1392.         $result  = &$_DB_DATAOBJECT['RESULTS'][$t->_DB_resultid];
  1393.         $l = $result->fetchRow();
  1394.         return $l[0];
  1395.     }
  1396.  
  1397.     /**
  1398.      * sends raw query to database
  1399.      *
  1400.      * Since _query has to be a private 'non overwriteable method', this is a relay
  1401.      *
  1402.      * @param  string  $string  SQL Query
  1403.      * @access public
  1404.      * @return void or DB_Error
  1405.      */
  1406.     function query($string)
  1407.     {
  1408.         return $this->_query($string);
  1409.     }
  1410.  
  1411.  
  1412.     /**
  1413.      * an escape wrapper around DB->escapeSimple()
  1414.      * can be used when adding manual queries or clauses
  1415.      * eg.
  1416.      * $object->query("select * from xyz where abc like '". $object->escape($_GET['name']) . "'");
  1417.      *
  1418.      * @param  string  $string  value to be escaped 
  1419.      * @access public
  1420.      * @return string
  1421.      */
  1422.     function escape($string)
  1423.     {
  1424.         global $_DB_DATAOBJECT;
  1425.         $this->_connect();
  1426.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1427.         return $DB->escapeSimple($string);
  1428.     }
  1429.  
  1430.     /* ==================================================== */
  1431.     /*        Major Private Vars                            */
  1432.     /* ==================================================== */
  1433.  
  1434.     /**
  1435.      * The Database connection dsn (as described in the PEAR DB)
  1436.      * only used really if you are writing a very simple application/test..
  1437.      * try not to use this - it is better stored in configuration files..
  1438.      *
  1439.      * @access  private
  1440.      * @var     string
  1441.      */
  1442.     var $_database_dsn = '';
  1443.  
  1444.     /**
  1445.      * The Database connection id (md5 sum of databasedsn)
  1446.      *
  1447.      * @access  private
  1448.      * @var     string
  1449.      */
  1450.     var $_database_dsn_md5 = '';
  1451.  
  1452.     /**
  1453.      * The Database name
  1454.      * created in __connection
  1455.      *
  1456.      * @access  private
  1457.      * @var  string
  1458.      */
  1459.     var $_database = '';
  1460.  
  1461.     
  1462.     
  1463.     /**
  1464.      * The QUERY rules
  1465.      * This replaces alot of the private variables 
  1466.      * used to build a query, it is unset after find() is run.
  1467.      * 
  1468.      *
  1469.      *
  1470.      * @access  private
  1471.      * @var     array
  1472.      */
  1473.     var $_query = array(
  1474.         'condition'   => '', // the WHERE condition
  1475.         'group_by'    => '', // the GROUP BY condition
  1476.         'order_by'    => '', // the ORDER BY condition
  1477.         'having'      => '', // the HAVING condition
  1478.         'limit'       => '', // the LIMIT condition
  1479.         'data_select' => '*', // the columns to be SELECTed
  1480.     );
  1481.         
  1482.     
  1483.   
  1484.  
  1485.     /**
  1486.      * Database result id (references global $_DB_DataObject[results]
  1487.      *
  1488.      * @access  private
  1489.      * @var     integer
  1490.      */
  1491.     var $_DB_resultid; // database result object
  1492.  
  1493.  
  1494.     /* ============================================================== */
  1495.     /*  Table definition layer (started of very private but 'came out'*/
  1496.     /* ============================================================== */
  1497.  
  1498.     /**
  1499.      * Autoload or manually load the table definitions
  1500.      *
  1501.      *
  1502.      * usage :
  1503.      * DB_DataObject::databaseStructure(  'databasename',
  1504.      *                                    parse_ini_file('mydb.ini',true), 
  1505.      *                                    parse_ini_file('mydb.link.ini',true)); 
  1506.      *
  1507.      * obviously you dont have to use ini files.. (just return array similar to ini files..)
  1508.      *  
  1509.      * It should append to the table structure array 
  1510.      *
  1511.      *     
  1512.      * @param optional string  name of database to assign / read
  1513.      * @param optional array   structure of database, and keys
  1514.      * @param optional array  table links
  1515.      *
  1516.      * @access public
  1517.      * @return true or PEAR:error on wrong paramenters.. or false if no file exists..
  1518.      *              or the array(tablename => array(column_name=>type)) if called with 1 argument.. (databasename)
  1519.      */
  1520.     function databaseStructure()
  1521.     {
  1522.  
  1523.         global $_DB_DATAOBJECT;
  1524.         
  1525.         // Assignment code 
  1526.         
  1527.         if ($args = func_get_args()) {
  1528.         
  1529.             if (count($args) == 1) {
  1530.                 
  1531.                 // this returns all the tables and their structure..
  1532.                 
  1533.                 $x = new DB_DataObject;
  1534.                 $x->_database = $args[0];
  1535.                 $this->_connect();
  1536.                 $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1537.        
  1538.                 $tables = $DB->getListOf('tables');
  1539.                 require_once 'DB/DataObject/Generator.php';
  1540.                 foreach($tables as $table) {
  1541.                     $y = new DB_DataObject_Generator;
  1542.                     $y->fillTableSchema($x->_database,$table);
  1543.                 }
  1544.                 return $_DB_DATAOBJECT['INI'][$x->_database];            
  1545.             } else {
  1546.         
  1547.                 $_DB_DATAOBJECT['INI'][$args[0]] = isset($_DB_DATAOBJECT['INI'][$args[0]]) ?
  1548.                     $_DB_DATAOBJECT['INI'][$args[0]] + $args[1] : $args[1];
  1549.                 
  1550.                 if (isset($args[1])) {
  1551.                     $_DB_DATAOBJECT['LINKS'][$args[0]] = isset($_DB_DATAOBJECT['LINKS'][$args[0]]) ?
  1552.                         $_DB_DATAOBJECT['LINKS'][$args[0]] + $args[2] : $args[2];
  1553.                 }
  1554.                 return true;
  1555.             }
  1556.           
  1557.         }
  1558.         
  1559.         
  1560.         
  1561.         if (!$this->_database) {
  1562.             $this->_connect();
  1563.         }
  1564.         
  1565.         // loaded already?
  1566.         if (!empty($_DB_DATAOBJECT['INI'][$this->_database])) {
  1567.             // database loaded - but this is table is not available..
  1568.             if (empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
  1569.                 require_once 'DB/DataObject/Generator.php';
  1570.                 $x = new DB_DataObject_Generator;
  1571.                 $x->fillTableSchema($this->_database,$this->__table);
  1572.             }
  1573.             return true;
  1574.         }
  1575.         
  1576.         
  1577.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  1578.             DB_DataObject::_loadConfig();
  1579.         }
  1580.         
  1581.         // if you supply this with arguments, then it will take those
  1582.         // as the database and links array...
  1583.          
  1584.         $schemas = isset($_DB_DATAOBJECT['CONFIG']['schema_location']) ?
  1585.             array("{$_DB_DATAOBJECT['CONFIG']['schema_location']}/{$this->_database}.ini") :
  1586.             array() ;
  1587.                  
  1588.         if (isset($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"])) {
  1589.             $schemas = is_array($_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]) ?
  1590.                 $_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"] :
  1591.                 explode(PATH_SEPARATOR,$_DB_DATAOBJECT['CONFIG']["ini_{$this->_database}"]);
  1592.         }
  1593.                     
  1594.          
  1595.         
  1596.         foreach ($schemas as $ini) {
  1597.             $links =
  1598.                 isset($_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"]) ?
  1599.                     $_DB_DATAOBJECT['CONFIG']["links_{$this->_database}"] :
  1600.                     str_replace('.ini','.links.ini',$ini);
  1601.  
  1602.             if (file_exists($ini)) {
  1603.                 $_DB_DATAOBJECT['INI'][$this->_database] = parse_ini_file($ini, true);
  1604.             }
  1605.             if (empty($_DB_DATAOBJECT['LINKS'][$this->_database]) && file_exists($links)) {
  1606.                 /* not sure why $links = ... here  - TODO check if that works */
  1607.                 $_DB_DATAOBJECT['LINKS'][$this->_database] = parse_ini_file($links, true);
  1608.             }
  1609.         }
  1610.         // now have we loaded the structure.. - if not try building it..
  1611.         
  1612.         if (empty($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
  1613.             require_once 'DB/DataObject/Generator.php';
  1614.             $x = new DB_DataObject_Generator;
  1615.             $x->fillTableSchema($this->_database,$this->__table);
  1616.         }
  1617.     
  1618.         
  1619.         return true;
  1620.     }
  1621.  
  1622.  
  1623.  
  1624.  
  1625.     /**
  1626.      * Return or assign the name of the current table
  1627.      *
  1628.      *
  1629.      * @param   string optinal table name to set
  1630.      * @access public
  1631.      * @return string The name of the current table
  1632.      */
  1633.     function tableName()
  1634.     {
  1635.         $args = func_get_args();
  1636.         if (count($args)) {
  1637.             $this->__table = $args[0];
  1638.         }
  1639.         return $this->__table;
  1640.     }
  1641.     
  1642.     /**
  1643.      * Return or assign the name of the current database
  1644.      *
  1645.      * @param   string optional database name to set
  1646.      * @access public
  1647.      * @return string The name of the current database
  1648.      */
  1649.     function database()
  1650.     {
  1651.         $args = func_get_args();
  1652.         if (count($args)) {
  1653.             $this->_database = $args[0];
  1654.         }
  1655.         return $this->_database;
  1656.     }
  1657.   
  1658.     /**
  1659.      * get/set an associative array of table columns
  1660.      *
  1661.      * @access public
  1662.      * @param  array key=>type array
  1663.      * @return array (associative)
  1664.      */
  1665.     function table()
  1666.     {
  1667.         
  1668.         // for temporary storage of database fields..
  1669.         // note this is not declared as we dont want to bloat the print_r output
  1670.         $args = func_get_args();
  1671.         if (count($args)) {
  1672.             $this->_database_fields = $args[0];
  1673.         }
  1674.         if (isset($this->_database_fields)) {
  1675.             return $this->_database_fields;
  1676.         }
  1677.         
  1678.         
  1679.         global $_DB_DATAOBJECT;
  1680.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  1681.             $this->_connect();
  1682.         }
  1683.         
  1684.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
  1685.             return $_DB_DATAOBJECT['INI'][$this->_database][$this->__table];
  1686.         }
  1687.         
  1688.         $this->databaseStructure();
  1689.  
  1690.         
  1691.         $ret = array();
  1692.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table])) {
  1693.             $ret =  $_DB_DATAOBJECT['INI'][$this->_database][$this->__table];
  1694.         }
  1695.         
  1696.         return $ret;
  1697.     }
  1698.  
  1699.     /**
  1700.      * get/set an  array of table primary keys
  1701.      *
  1702.      * set usage: $do->keys('id','code');
  1703.      *
  1704.      * This is defined in the table definition if it gets it wrong,
  1705.      * or you do not want to use ini tables, you can override this.
  1706.      * @param  string optional set the key
  1707.      * @param  *   optional  set more keys
  1708.      * @access private
  1709.      * @return array
  1710.      */
  1711.     function keys()
  1712.     {
  1713.         // for temporary storage of database fields..
  1714.         // note this is not declared as we dont want to bloat the print_r output
  1715.         $args = func_get_args();
  1716.         if (count($args)) {
  1717.             $this->_database_keys = $args;
  1718.         }
  1719.         if (isset($this->_database_keys)) {
  1720.             return $this->_database_keys;
  1721.         }
  1722.         
  1723.         global $_DB_DATAOBJECT;
  1724.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  1725.             $this->_connect();
  1726.         }
  1727.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
  1728.             return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]);
  1729.         }
  1730.         $this->databaseStructure();
  1731.         
  1732.         if (isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"])) {
  1733.             return array_keys($_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"]);
  1734.         }
  1735.         return array();
  1736.     }
  1737.     /**
  1738.      * get/set an  sequence key
  1739.      *
  1740.      * by default it returns the first key from keys()
  1741.      * set usage: $do->sequenceKey('id',true);
  1742.      *
  1743.      * override this to return array(false,false) if table has no real sequence key.
  1744.      *
  1745.      * @param  string  optional the key sequence/autoinc. key
  1746.      * @param  boolean optional use native increment. default false 
  1747.      * @param  false|string optional native sequence name
  1748.      * @access private
  1749.      * @return array (column,use_native,sequence_name)
  1750.      */
  1751.     function sequenceKey()
  1752.     {
  1753.         global $_DB_DATAOBJECT;
  1754.           
  1755.         // call setting
  1756.         if (!$this->_database) {
  1757.             $this->_connect();
  1758.         }
  1759.         
  1760.         if (!isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database])) {
  1761.             $_DB_DATAOBJECT['SEQUENCE'][$this->_database] = array();
  1762.         }
  1763.  
  1764.         
  1765.         $args = func_get_args();
  1766.         if (count($args)) {
  1767.             $args[1] = isset($args[1]) ? $args[1] : false;
  1768.             $args[2] = isset($args[2]) ? $args[2] : false;
  1769.             $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = $args;
  1770.         }
  1771.         if (isset($_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table])) {
  1772.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table];
  1773.         }
  1774.         // end call setting (eg. $do->sequenceKeys(a,b,c); )
  1775.         
  1776.        
  1777.         
  1778.         
  1779.         $keys = $this->keys();
  1780.         if (!$keys) {
  1781.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] 
  1782.                 = array(false,false,false);;
  1783.         }
  1784.  
  1785.  
  1786.         $table =  isset($_DB_DATAOBJECT['INI'][$this->_database][$this->__table]) ?   
  1787.             $_DB_DATAOBJECT['INI'][$this->_database][$this->__table] : $this->table();
  1788.        
  1789.         $dbtype    = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['phptype'];
  1790.         
  1791.         $usekey = $keys[0];
  1792.         
  1793.         
  1794.         
  1795.         $seqname = false;
  1796.         
  1797.         if (!empty($_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table])) {
  1798.             $usekey = $_DB_DATAOBJECT['CONFIG']['sequence_'.$this->__table];
  1799.             if (strpos($usekey,':') !== false) {
  1800.                 list($usekey,$seqname) = explode(':',$usekey);
  1801.             }
  1802.         }  
  1803.         
  1804.         
  1805.         // if the key is not an integer - then it's not a sequence or native
  1806.         if (!($table[$usekey] & DB_DATAOBJECT_INT)) {
  1807.                 return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,false);
  1808.         }
  1809.         
  1810.         
  1811.         if (!empty($_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'])) {
  1812.             $ignore =  $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'];
  1813.             if (is_string($ignore) && (strtoupper($ignore) == 'ALL')) {
  1814.                 return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
  1815.             }
  1816.             if (is_string($ignore)) {
  1817.                 $ignore = $_DB_DATAOBJECT['CONFIG']['ignore_sequence_keys'] = explode(',',$ignore);
  1818.             }
  1819.             if (in_array($this->__table,$ignore)) {
  1820.                 return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
  1821.             }
  1822.         }
  1823.         
  1824.         
  1825.         $realkeys = $_DB_DATAOBJECT['INI'][$this->_database][$this->__table."__keys"];
  1826.         
  1827.         // if you are using an old ini file - go back to old behaviour...
  1828.         if (is_numeric($realkeys[$usekey])) {
  1829.             $realkeys[$usekey] = 'N';
  1830.         }
  1831.         
  1832.         // multiple unique primary keys without a native sequence...
  1833.         if (($realkeys[$usekey] == 'K') && (count($keys) > 1)) {
  1834.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array(false,false,$seqname);
  1835.         }
  1836.         // use native sequence keys...
  1837.         // technically postgres native here...
  1838.         // we need to get the new improved tabledata sorted out first.
  1839.         
  1840.         if (    in_array($dbtype , array( 'mysql', 'mssql')) && 
  1841.                 ($table[$usekey] & DB_DATAOBJECT_INT) && 
  1842.                 isset($realkeys[$usekey]) && ($realkeys[$usekey] == 'N')
  1843.                 ) {
  1844.             return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array($usekey,true,$seqname);
  1845.         }
  1846.         // if not a native autoinc, and we have not assumed all primary keys are sequence
  1847.         if (($realkeys[$usekey] != 'N') && 
  1848.             !empty($_DB_DATAOBJECT['CONFIG']['dont_use_pear_sequences'])) {
  1849.             return array(false,false,false);
  1850.         }
  1851.         // I assume it's going to try and be a nextval DB sequence.. (not native)
  1852.         return $_DB_DATAOBJECT['SEQUENCE'][$this->_database][$this->__table] = array($usekey,false,$seqname);
  1853.     }
  1854.     
  1855.     
  1856.     
  1857.     /* =========================================================== */
  1858.     /*  Major Private Methods - the core part!              */
  1859.     /* =========================================================== */
  1860.  
  1861.  
  1862.     
  1863.     /**
  1864.      * clear the cache values for this class  - normally done on insert/update etc.
  1865.      *
  1866.      * @access private
  1867.      * @return void
  1868.      */
  1869.     function _clear_cache()
  1870.     {
  1871.         global $_DB_DATAOBJECT;
  1872.         
  1873.         $class = get_class($this);
  1874.         
  1875.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1876.             $this->debug("Clearing Cache for ".$class,1);
  1877.         }
  1878.         
  1879.         if (!empty($_DB_DATAOBJECT['CACHE'][$class])) {
  1880.             unset($_DB_DATAOBJECT['CACHE'][$class]);
  1881.         }
  1882.     }
  1883.  
  1884.     /**
  1885.      * connects to the database
  1886.      *
  1887.      *
  1888.      * TODO: tidy this up - This has grown to support a number of connection options like
  1889.      *  a) dynamic changing of ini file to change which database to connect to
  1890.      *  b) multi data via the table_{$table} = dsn ini option
  1891.      *  c) session based storage.
  1892.      *
  1893.      * @access private
  1894.      * @return true | PEAR::error
  1895.      */
  1896.     function _connect()
  1897.     {
  1898.         global $_DB_DATAOBJECT;
  1899.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  1900.             $this->_loadConfig();
  1901.         }
  1902.  
  1903.         // is it already connected ?
  1904.  
  1905.         if ($this->_database_dsn_md5 && !empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  1906.             if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  1907.                 return $this->raiseError(
  1908.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->message,
  1909.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, PEAR_ERROR_DIE
  1910.                 );
  1911.                  
  1912.             }
  1913.  
  1914.             if (!$this->_database) {
  1915.                 $this->_database = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn['database'];
  1916.             }
  1917.             
  1918.         }
  1919.  
  1920.         // it's not currently connected!
  1921.         // try and work out what to use for the dsn !
  1922.  
  1923.         $options= &$_DB_DATAOBJECT['CONFIG'];
  1924.         $dsn = isset($this->_database_dsn) ? $this->_database_dsn : null;
  1925.  
  1926.         if (!$dsn) {
  1927.             if (!$this->_database) {
  1928.                 $this->_database = isset($options["table_{$this->__table}"]) ?$options["table_{$this->__table}"] : null;
  1929.             }
  1930.             if ($this->_database && !empty($options["database_{$this->_database}"]))  {
  1931.                 $dsn = $options["database_{$this->_database}"];
  1932.             } else if (!empty($options['database'])) {
  1933.                 $dsn = $options['database'];
  1934.             }
  1935.         }
  1936.         
  1937.         // if still no database...
  1938.         if (!$dsn) {
  1939.             return $this->raiseError(
  1940.                 "No database name / dsn found anywhere",
  1941.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG, PEAR_ERROR_DIE
  1942.             );
  1943.                  
  1944.         }
  1945.         
  1946.         
  1947.  
  1948.         $this->_database_dsn_md5 = md5($dsn);
  1949.  
  1950.         if (!empty($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  1951.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1952.                 $this->debug("USING CACHED CONNECTION", "CONNECT",3);
  1953.             }
  1954.             if (!$this->_database) {
  1955.                 $this->_database = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["database"];
  1956.             }
  1957.             return true;
  1958.         }
  1959.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1960.             $this->debug("NEW CONNECTION", "CONNECT",3);
  1961.             /* actualy make a connection */
  1962.             $this->debug("{$dsn} {$this->_database_dsn_md5}", "CONNECT",3);
  1963.         }
  1964.         $db_options = PEAR::getStaticProperty('DB','options');
  1965.         require_once 'DB.php';
  1966.         if ($db_options) {
  1967.  
  1968.             $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn,$db_options);
  1969.         } else {
  1970.             $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5] = DB::connect($dsn);
  1971.         }
  1972.         
  1973.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  1974.             $this->debug(serialize($_DB_DATAOBJECT['CONNECTIONS']), "CONNECT",5);
  1975.         }
  1976.         if (PEAR::isError($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  1977.             return $this->raiseError(
  1978.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->message,
  1979.                         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->code, PEAR_ERROR_DIE
  1980.             );
  1981.  
  1982.         }
  1983.  
  1984.         if (!$this->_database) {
  1985.             $this->_database = $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->dsn["database"];
  1986.         }
  1987.         
  1988.         // Oracle need to optimize for portibility - not sure exactly what this does though :)
  1989.         $c = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  1990.         
  1991.         
  1992.         
  1993.         
  1994.         
  1995.         
  1996.  
  1997.         return true;
  1998.     }
  1999.  
  2000.     /**
  2001.      * sends query to database - this is the private one that must work 
  2002.      *   - internal functions use this rather than $this->query()
  2003.      *
  2004.      * @param  string  $string
  2005.      * @access private
  2006.      * @return mixed none or PEAR_Error
  2007.      */
  2008.     function _query($string)
  2009.     {
  2010.         global $_DB_DATAOBJECT;
  2011.         $this->_connect();
  2012.         
  2013.  
  2014.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  2015.  
  2016.         $options = &$_DB_DATAOBJECT['CONFIG'];
  2017.  
  2018.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2019.             $this->debug($string,$log="QUERY");
  2020.             
  2021.         }
  2022.         
  2023.         if (strtoupper($string) == 'BEGIN') {
  2024.             $DB->autoCommit(false);
  2025.             // db backend adds begin anyway from now on..
  2026.             return true;
  2027.         }
  2028.         if (strtoupper($string) == 'COMMIT') {
  2029.             $DB->commit();
  2030.             $DB->autoCommit(true);
  2031.             return true;
  2032.         }
  2033.         
  2034.         if (strtoupper($string) == 'ROLLBACK') {
  2035.             $DB->rollback();
  2036.             $DB->autoCommit(true);
  2037.             return true;
  2038.         }
  2039.         
  2040.  
  2041.         if (!empty($options['debug_ignore_updates']) &&
  2042.             (strtolower(substr(trim($string), 0, 6)) != 'select') &&
  2043.             (strtolower(substr(trim($string), 0, 4)) != 'show') &&
  2044.             (strtolower(substr(trim($string), 0, 8)) != 'describe')) {
  2045.  
  2046.             $this->debug('Disabling Update as you are in debug mode');
  2047.             return $this->raiseError("Disabling Update as you are in debug mode", null) ;
  2048.  
  2049.         }
  2050.         //if (@$_DB_DATAOBJECT['CONFIG']['debug'] > 1) {
  2051.             // this will only work when PEAR:DB supports it.
  2052.             //$this->debug($DB->getAll('explain ' .$string,DB_FETCHMODE_ASSOC), $log="sql",2);
  2053.         //}
  2054.         
  2055.         // some sim
  2056.         $t= explode(' ',microtime());
  2057.         $_DB_DATAOBJECT['QUERYENDTIME'] = $time = $t[0]+$t[1];
  2058.          
  2059.         $result = $DB->query($string);
  2060.         
  2061.         
  2062.        
  2063.  
  2064.         if (is_a($result,'DB_Error')) {
  2065.             if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) { 
  2066.                 $this->debug($result->toString(), "Query Error",1 );
  2067.             }
  2068.             return $result;
  2069.         }
  2070.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2071.             $t= explode(' ',microtime());
  2072.             $_DB_DATAOBJECT['QUERYENDTIME'] = $t[0]+$t[1];
  2073.             $this->debug('QUERY DONE IN  '.($t[0]+$t[1]-$time)." seconds", 'query',1);
  2074.         }
  2075.         switch (strtolower(substr(trim($string),0,6))) {
  2076.             case 'insert':
  2077.             case 'update':
  2078.             case 'delete':
  2079.                 return $DB->affectedRows();;
  2080.         }
  2081.         if (is_object($result)) {
  2082.             // lets hope that copying the result object is OK!
  2083.             
  2084.             $_DB_resultid  = $GLOBALS['_DB_DATAOBJECT']['RESULTSEQ']++;
  2085.             $_DB_DATAOBJECT['RESULTS'][$_DB_resultid] = $result; 
  2086.             $this->_DB_resultid = $_DB_resultid;
  2087.         }
  2088.         $this->N = 0;
  2089.         if (!empty($_DB_DATAOBJECT['CONFIG']['debug'])) {
  2090.             $this->debug(serialize($result), 'RESULT',5);
  2091.         }
  2092.         if (method_exists($result, 'numrows')) {
  2093.             $DB->expectError(DB_ERROR_UNSUPPORTED);
  2094.             $this->N = $result->numrows();
  2095.             if (is_a($this->N,'DB_Error')) {
  2096.                 $this->N = 1;
  2097.             }
  2098.             $DB->popExpect();
  2099.         }
  2100.     }
  2101.  
  2102.     /**
  2103.      * Builds the WHERE based on the values of of this object
  2104.      *
  2105.      * @param   mixed   $keys
  2106.      * @param   array   $filter (used by update to only uses keys in this filter list).
  2107.      * @param   array   $negative_filter (used by delete to prevent deleting using the keys mentioned..)
  2108.      * @access  private
  2109.      * @return  string
  2110.      */
  2111.     function _build_condition($keys, $filter = array(),$negative_filter=array())
  2112.     {
  2113.         global $_DB_DATAOBJECT;
  2114.         $this->_connect();
  2115.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  2116.        
  2117.         $quoteIdentifiers  = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  2118.         // if we dont have query vars.. - reset them.
  2119.         if (!isset($this->_query)) {
  2120.             $x = new DB_DataObject;
  2121.             $this->_query= $x->_query;
  2122.         }
  2123.  
  2124.         foreach($keys as $k => $v) {
  2125.             // index keys is an indexed array
  2126.             /* these filter checks are a bit suspicious..
  2127.                 - need to check that update really wants to work this way */
  2128.  
  2129.             if ($filter) {
  2130.                 if (!in_array($k, $filter)) {
  2131.                     continue;
  2132.                 }
  2133.             }
  2134.             if ($negative_filter) {
  2135.                 if (in_array($k, $negative_filter)) {
  2136.                     continue;
  2137.                 }
  2138.             }
  2139.             if (!isset($this->$k)) {
  2140.                 continue;
  2141.             }
  2142.             
  2143.             $kSql = $quoteIdentifiers 
  2144.                 ? ( $DB->quoteIdentifier($this->__table) . '.' . $DB->quoteIdentifier($k) )  
  2145.                 : "{$this->__table}.{$k}";
  2146.              
  2147.              
  2148.             
  2149.             if (is_a($this->$k,'db_dataobject_cast')) {
  2150.                 $dbtype = $DB->dsn["phptype"];
  2151.                 $value = $this->$k->toString($v,$dbtype);
  2152.                 if (PEAR::isError($value)) {
  2153.                     $this->raiseError($value->getMessage() ,DB_DATAOBJECT_ERROR_INVALIDARG);
  2154.                     return false;
  2155.                 }
  2156.                 if ($value == 'NULL') {
  2157.                     $value = 'IS NULL';
  2158.                 }
  2159.                 $this->whereAdd(" $kSql = $value");
  2160.                 continue;
  2161.             }
  2162.             
  2163.             if ((strtolower($this->$k) === 'null') && !($v & DB_DATAOBJECT_NOTNULL)) {
  2164.                 $this->whereAdd(" $kSql  IS NULL");
  2165.                 continue;
  2166.             }
  2167.             
  2168.  
  2169.             if ($v & DB_DATAOBJECT_STR) {
  2170.                 $this->whereAdd(" $kSql  = " . $DB->quote($this->$k) );
  2171.                 continue;
  2172.             }
  2173.             if (is_numeric($this->$k)) {
  2174.                 $this->whereAdd(" $kSql = {$this->$k}");
  2175.                 continue;
  2176.             }
  2177.             /* this is probably an error condition! */
  2178.             $this->whereAdd(" $kSql = 0");
  2179.         }
  2180.     }
  2181.  
  2182.     /**
  2183.      * autoload Class relating to a table
  2184.      * (depreciated - use ::factory)
  2185.      *
  2186.      * @param  string  $table  table
  2187.      * @access private
  2188.      * @return string classname on Success
  2189.      */
  2190.     function staticAutoloadTable($table)
  2191.     {
  2192.         global $_DB_DATAOBJECT;
  2193.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2194.             DB_DataObject::_loadConfig();
  2195.         }
  2196.         $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
  2197.             $_DB_DATAOBJECT['CONFIG']['class_prefix'] : '';
  2198.         $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
  2199.         $class = (class_exists($class)) ? $class  : DB_DataObject::_autoloadClass($class);
  2200.         return $class;
  2201.     }
  2202.     
  2203.     
  2204.      /**
  2205.      * classic factory method for loading a table class
  2206.      * usage: $do = DB_DataObject::factory('person')
  2207.      * WARNING - this may emit a include error if the file does not exist..
  2208.      * use @ to silence it (if you are sure it is acceptable)
  2209.      * eg. $do = @DB_DataObject::factory('person')
  2210.      *
  2211.      * table name will eventually be databasename/table
  2212.      * - and allow modular dataobjects to be written..
  2213.      * (this also helps proxy creation)
  2214.      *
  2215.      *
  2216.      * @param  string  $table  tablename (use blank to create a new instance of the same class.)
  2217.      * @access private
  2218.      * @return DataObject|PEAR_Error 
  2219.      */
  2220.     
  2221.     
  2222.  
  2223.     function factory($table = '') {
  2224.         global $_DB_DATAOBJECT;
  2225.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2226.             DB_DataObject::_loadConfig();
  2227.         }
  2228.         
  2229.         if ($table === '') {
  2230.             if (is_a($this,'DB_DataObject') && strlen($this->__table)) {
  2231.                 $table = $this->__table;
  2232.             } else {
  2233.                 return DB_DataObject::raiseError(
  2234.                     "factory did not recieve a table name",
  2235.                     DB_DATAOBJECT_ERROR_INVALIDARGS);
  2236.             }
  2237.         }
  2238.         
  2239.         
  2240.         $p = isset($_DB_DATAOBJECT['CONFIG']['class_prefix']) ?
  2241.             $_DB_DATAOBJECT['CONFIG']['class_prefix'] : '';
  2242.         $class = $p . preg_replace('/[^A-Z0-9]/i','_',ucfirst($table));
  2243.         
  2244.         $class = (class_exists($class)) ? $class  : DB_DataObject::_autoloadClass($class);
  2245.         
  2246.         // proxy = full|light
  2247.         if (!$class && isset($_DB_DATAOBJECT['CONFIG']['proxy'])) { 
  2248.             $proxyMethod = 'getProxy'.$_DB_DATAOBJECT['CONFIG']['proxy'];
  2249.             
  2250.             require_once 'DB/DataObject/Generator.php';
  2251.             $d = new DB_DataObject;
  2252.            
  2253.             $d->__table = $table;
  2254.             $d->_connect();
  2255.             
  2256.             $x = new DB_DataObject_Generator;
  2257.             return $x->$proxyMethod( $d->_database, $table);
  2258.         }
  2259.         
  2260.         if (!$class) {
  2261.             return DB_DataObject::raiseError(
  2262.                 "factory could not find class $class from $table",
  2263.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2264.         }
  2265.  
  2266.         return new $class;
  2267.     }
  2268.     /**
  2269.      * autoload Class
  2270.      *
  2271.      * @param  string  $class  Class
  2272.      * @access private
  2273.      * @return string classname on Success
  2274.      */
  2275.     function _autoloadClass($class)
  2276.     {
  2277.         global $_DB_DATAOBJECT;
  2278.         
  2279.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2280.             DB_DataObject::_loadConfig();
  2281.         }
  2282.         $table   = substr($class,strlen($_DB_DATAOBJECT['CONFIG']['class_prefix']));
  2283.  
  2284.         // only include the file if it exists - and barf badly if it has parse errors :)
  2285.         if (!empty($_DB_DATAOBJECT['CONFIG']['proxy']) && empty($_DB_DATAOBJECT['CONFIG']['class_location'])) {
  2286.             return false;
  2287.         }
  2288.         
  2289.         $file = $_DB_DATAOBJECT['CONFIG']['class_location'].'/'.preg_replace('/[^A-Z0-9]/i','_',ucfirst($table)).".php";
  2290.         
  2291.         if (!file_exists($file)) {
  2292.             $found = false;
  2293.             foreach(explode(PATH_SEPARATOR, ini_get('include_path')) as $p) {
  2294.                 if (file_exists("$p/$file")) {
  2295.                     $file = "$p/$file";
  2296.                     $found = true;
  2297.                     break;
  2298.                 }
  2299.             }
  2300.             if (!$found) {
  2301.                 DB_DataObject::raiseError(
  2302.                     "autoload:Could not find class {$class} using class_location value", 
  2303.                     DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2304.                 return false;
  2305.             }
  2306.         }
  2307.         
  2308.         include_once $file;
  2309.         
  2310.         
  2311.         
  2312.         
  2313.         if (!class_exists($class)) {
  2314.             DB_DataObject::raiseError(
  2315.                 "autoload:Could not autoload {$class}", 
  2316.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2317.             return false;
  2318.         }
  2319.         return $class;
  2320.     }
  2321.     
  2322.     
  2323.     
  2324.     /**
  2325.      * Have the links been loaded?
  2326.      * if they have it contains a array of those variables.
  2327.      *
  2328.      * @access  private
  2329.      * @var     boolean | array
  2330.      */
  2331.     var $_link_loaded = false;
  2332.     
  2333.     /**
  2334.     * Get the links associate array  as defined by the links.ini file.
  2335.     * 
  2336.     *
  2337.     * Experimental... - 
  2338.     * Should look a bit like
  2339.     *       [local_col_name] => "related_tablename:related_col_name"
  2340.     * 
  2341.     * 
  2342.     * @return   array|null    
  2343.     *           array       = if there are links defined for this table.
  2344.     *           empty array - if there is a links.ini file, but no links on this table
  2345.     *           null        - if no links.ini exists for this database (hence try auto_links).
  2346.     * @access   public
  2347.     * @see      DB_DataObject::getLinks(), DB_DataObject::getLink()
  2348.     */
  2349.     
  2350.     function links()
  2351.     {
  2352.         global $_DB_DATAOBJECT;
  2353.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  2354.             $this->_loadConfig();
  2355.         }
  2356.         
  2357.         
  2358.         if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
  2359.             return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
  2360.         }
  2361.         $this->databaseStructure();
  2362.         // if there is no link data at all on the file!
  2363.         // we return null.
  2364.         if (!isset($_DB_DATAOBJECT['LINKS'][$this->_database])) {
  2365.             return null;
  2366.         }
  2367.         
  2368.         if (isset($_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table])) {
  2369.             return $_DB_DATAOBJECT['LINKS'][$this->_database][$this->__table];
  2370.         }
  2371.         
  2372.         return array();
  2373.     }
  2374.     /**
  2375.      * load related objects
  2376.      *
  2377.      * There are two ways to use this, one is to set up a <dbname>.links.ini file
  2378.      * into a static property named <dbname>.links and specifies the table joins,
  2379.      * the other highly dependent on naming columns 'correctly' :)
  2380.      * using colname = xxxxx_yyyyyy
  2381.      * xxxxxx = related table; (yyyyy = user defined..)
  2382.      * looks up table xxxxx, for value id=$this->xxxxx
  2383.      * stores it in $this->_xxxxx_yyyyy
  2384.      * you can change what object vars the links are stored in by 
  2385.      * changeing the format parameter
  2386.      *
  2387.      *
  2388.      * @param  string format (default _%s) where %s is the table name.
  2389.      * @author Tim White <tim@cyface.com>
  2390.      * @access public
  2391.      * @return boolean , true on success
  2392.      */
  2393.     function getLinks($format = '_%s')
  2394.     {
  2395.          
  2396.         // get table will load the options.
  2397.         if ($this->_link_loaded) {
  2398.             return true;
  2399.         }
  2400.         $this->_link_loaded = false;
  2401.         $cols  = $this->table();
  2402.         $links = $this->links();
  2403.          
  2404.         $loaded = array();
  2405.         
  2406.         if ($links) {   
  2407.             foreach($links as $key => $match) {
  2408.                 list($table,$link) = explode(':', $match);
  2409.                 $k = sprintf($format, str_replace('.', '_', $key));
  2410.                 // makes sure that '.' is the end of the key;
  2411.                 if ($p = strpos($key,'.')) {
  2412.                       $key = substr($key, 0, $p);
  2413.                 }
  2414.                 
  2415.                 $this->$k = $this->getLink($key, $table, $link);
  2416.                 if (is_object($this->$k)) {
  2417.                     $loaded[] = $k; 
  2418.                 }
  2419.             }
  2420.             $this->_link_loaded = $loaded;
  2421.             return true;
  2422.         }
  2423.         // this is the autonaming stuff..
  2424.         // it sends the column name down to getLink and lets that sort it out..
  2425.         // if there is a links file then it is not used!
  2426.         // IT IS DEPRECIATED!!!! - USE 
  2427.         if (!is_null($links)) {    
  2428.             return false;
  2429.         }
  2430.         
  2431.         
  2432.         foreach (array_keys($cols) as $key) {
  2433.             if (!($p = strpos($key, '_'))) {
  2434.                 continue;
  2435.             }
  2436.             // does the table exist.
  2437.             $k =sprintf($format, $key);
  2438.             $this->$k = $this->getLink($key);
  2439.             if (is_object($this->$k)) {
  2440.                 $loaded[] = $k; 
  2441.             }
  2442.         }
  2443.         $this->_link_loaded = $loaded;
  2444.         return true;
  2445.     }
  2446.  
  2447.     /**
  2448.      * return name from related object
  2449.      *
  2450.      * There are two ways to use this, one is to set up a <dbname>.links.ini file
  2451.      * into a static property named <dbname>.links and specifies the table joins,
  2452.      * the other is highly dependant on naming columns 'correctly' :)
  2453.      *
  2454.      * NOTE: the naming convention is depreciated!!! - use links.ini
  2455.      *
  2456.      * using colname = xxxxx_yyyyyy
  2457.      * xxxxxx = related table; (yyyyy = user defined..)
  2458.      * looks up table xxxxx, for value id=$this->xxxxx
  2459.      * stores it in $this->_xxxxx_yyyyy
  2460.      *
  2461.      * you can also use $this->getLink('thisColumnName','otherTable','otherTableColumnName')
  2462.      *
  2463.      *
  2464.      * @param string $row    either row or row.xxxxx
  2465.      * @param string $table  name of table to look up value in
  2466.      * @param string $link   name of column in other table to match
  2467.      * @author Tim White <tim@cyface.com>
  2468.      * @access public
  2469.      * @return mixed object on success
  2470.      */
  2471.     function &getLink($row, $table = null, $link = false)
  2472.     {
  2473.         
  2474.         
  2475.         // GUESS THE LINKED TABLE.. (if found - recursevly call self)
  2476.         
  2477.         if ($table === null) {
  2478.             $links = $this->links();
  2479.             
  2480.             if (is_array($links)) {
  2481.             
  2482.                 if ($links[$row]) {
  2483.                     list($table,$link) = explode(':', $links[$row]);
  2484.                     if ($p = strpos($row,".")) {
  2485.                         $row = substr($row,0,$p);
  2486.                     }
  2487.                     return $r = &$this->getLink($row,$table,$link);
  2488.                 } 
  2489.                 
  2490.                 $this->raiseError(
  2491.                     "getLink: $row is not defined as a link (normally this is ok)", 
  2492.                     DB_DATAOBJECT_ERROR_NODATA);
  2493.                     
  2494.                 return false; // technically a possible error condition?
  2495.  
  2496.             }  
  2497.             // use the old _ method - this shouldnt happen if called via getLinks()
  2498.             if (!($p = strpos($row, '_'))) {
  2499.                 return null; 
  2500.             }
  2501.             $table = substr($row, 0, $p);
  2502.             return $r = &$this->getLink($row, $table);
  2503.  
  2504.         }
  2505.         
  2506.         
  2507.         
  2508.         if (!isset($this->$row)) {
  2509.             $this->raiseError("getLink: row not set $row", DB_DATAOBJECT_ERROR_NODATA);
  2510.             return false;
  2511.         }
  2512.         
  2513.         // check to see if we know anything about this table..
  2514.         
  2515.         $obj = $this->factory($table);
  2516.         
  2517.         if (!is_a($obj,'DB_DataObject')) {
  2518.             $this->raiseError(
  2519.                 "getLink:Could not find class for row $row, table $table", 
  2520.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2521.             return false;
  2522.         }
  2523.         if ($link) {
  2524.             if ($obj->get($link, $this->$row)) {
  2525.                 return $obj;
  2526.             } 
  2527.             return false;
  2528.         }
  2529.         
  2530.         if ($obj->get($this->$row)) {
  2531.             return $obj;
  2532.         }
  2533.         return false;
  2534.     }
  2535.  
  2536.     /**
  2537.      * IS THIS SUPPORTED/USED ANYMORE???? 
  2538.      *return a list of options for a linked table
  2539.      *
  2540.      * This is highly dependant on naming columns 'correctly' :)
  2541.      * using colname = xxxxx_yyyyyy
  2542.      * xxxxxx = related table; (yyyyy = user defined..)
  2543.      * looks up table xxxxx, for value id=$this->xxxxx
  2544.      * stores it in $this->_xxxxx_yyyyy
  2545.      *
  2546.      * @access public
  2547.      * @return array of results (empty array on failure)
  2548.      */
  2549.     function &getLinkArray($row, $table = null)
  2550.     {
  2551.         
  2552.         $ret = array();
  2553.         if (!$table) {
  2554.             $links = $this->links();
  2555.             
  2556.             if (is_array($links)) {
  2557.                 if (!isset($links[$row])) {
  2558.                     // failed..
  2559.                     return $ret;
  2560.                 }
  2561.                 list($table,$link) = explode(':',$links[$row]);
  2562.             } else {
  2563.                 if (!($p = strpos($row,'_'))) {
  2564.                     return $ret;
  2565.                 }
  2566.                 $table = substr($row,0,$p);
  2567.             }
  2568.         }
  2569.         
  2570.         $c  = $this->factory($table);
  2571.         
  2572.         if (!is_a($obj,'DB_DataObject')) {
  2573.             $this->raiseError(
  2574.                 "getLinkArray:Could not find class for row $row, table $table", 
  2575.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG
  2576.             );
  2577.             return $ret;
  2578.         }
  2579.  
  2580.         // if the user defined method list exists - use it...
  2581.         if (method_exists($c, 'listFind')) {
  2582.             $c->listFind($this->id);
  2583.         } else {
  2584.             $c->find();
  2585.         }
  2586.         while ($c->fetch()) {
  2587.             $ret[] = $c;
  2588.         }
  2589.         return $ret;
  2590.     }
  2591.  
  2592.     /**
  2593.      * The JOIN condition
  2594.      *
  2595.      * @access  private
  2596.      * @var     string
  2597.      */
  2598.     var $_join = '';
  2599.  
  2600.     /**
  2601.      * joinAdd - adds another dataobject to this, building a joined query.
  2602.      *
  2603.      * example (requires links.ini to be set up correctly)
  2604.      * // get all the images for product 24
  2605.      * $i = new DataObject_Image();
  2606.      * $pi = new DataObjects_Product_image();
  2607.      * $pi->product_id = 24; // set the product id to 24
  2608.      * $i->joinAdd($pi); // add the product_image connectoin
  2609.      * $i->find();
  2610.      * while ($i->fetch()) {
  2611.      *     // do stuff
  2612.      * }
  2613.      * // an example with 2 joins
  2614.      * // get all the images linked with products or productgroups
  2615.      * $i = new DataObject_Image();
  2616.      * $pi = new DataObject_Product_image();
  2617.      * $pgi = new DataObject_Productgroup_image();
  2618.      * $i->joinAdd($pi);
  2619.      * $i->joinAdd($pgi);
  2620.      * $i->find();
  2621.      * while ($i->fetch()) {
  2622.      *     // do stuff
  2623.      * }
  2624.      *
  2625.      *
  2626.      * @param    optional $obj       object |array    the joining object (no value resets the join)
  2627.      *                                          If you use an array here it should be in the format:
  2628.      *                                          array('local_column','remotetable:remote_column');
  2629.      *                                          if remotetable does not have a definition, you should
  2630.      *                                          use @ to hide the include error message..
  2631.      *                                      
  2632.      *
  2633.      * @param    optional $joinType  string     'LEFT'|'INNER'|'RIGHT'|'' Inner is default, '' indicates 
  2634.      *                                          just select ... from a,b,c with no join and 
  2635.      *                                          links are added as where items.
  2636.      *
  2637.      * @param    optional $joinAs    string     if you want to select the table as anther name
  2638.      *                                          useful when you want to select multiple columsn
  2639.      *                                          from a secondary table.
  2640.      
  2641.      * @param    optional $joinCol   string     The column on This objects table to match (needed
  2642.      *                                          if this table links to the child object in 
  2643.      *                                          multiple places eg.
  2644.      *                                          user->friend (is a link to another user)
  2645.      *                                          user->mother (is a link to another user..)
  2646.      *
  2647.      * @return   none
  2648.      * @access   public
  2649.      * @author   Stijn de Reede      <sjr@gmx.co.uk>
  2650.      */
  2651.     function joinAdd($obj = false, $joinType='INNER', $joinAs=false, $joinCol=false)
  2652.     {
  2653.         global $_DB_DATAOBJECT;
  2654.         if ($obj === false) {
  2655.             $this->_join = '';
  2656.             return;
  2657.         }
  2658.         
  2659.         // support for array as first argument 
  2660.         // this assumes that you dont have a links.ini for the specified table.
  2661.         // and it doesnt exist as am extended dataobject!! - experimental.
  2662.         
  2663.         $ofield = false; // object field
  2664.         $tfield = false; // this field
  2665.         $toTable = false;
  2666.         if (is_array($obj)) {
  2667.             $tfield = $obj[0];
  2668.             list($toTable,$ofield) = explode(':',$obj[1]);
  2669.             $obj = DB_DataObject::factory($toTable);
  2670.             if (!$obj) {
  2671.                 $obj = new DB_DataObject;
  2672.                 $obj->__table = $toTable;
  2673.             }
  2674.             // set the table items to nothing.. - eg. do not try and match
  2675.             // things in the child table...???
  2676.             $items = array();
  2677.         }
  2678.         
  2679.         if (!is_object($obj)) {
  2680.             $this->raiseError("joinAdd: called without an object", DB_DATAOBJECT_ERROR_NODATA,PEAR_ERROR_DIE);
  2681.         }
  2682.         /*  make sure $this->_database is set.  */
  2683.         $this->_connect();
  2684.         $DB = &$_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  2685.        
  2686.  
  2687.         
  2688.         
  2689.          /* look up the links for obj table */
  2690.  
  2691.         if (!$ofield && ($olinks = $obj->links())) {
  2692.             foreach ($olinks as $k => $v) {
  2693.                 /* link contains {this column} = {linked table}:{linked column} */
  2694.                 $ar = explode(':', $v);
  2695.                 if ($ar[0] == $this->__table) {
  2696.                     
  2697.                     // you have explictly specified the column
  2698.                     // and the col is listed here..
  2699.                     // not sure if 1:1 table could cause probs here..
  2700.                     
  2701.                     if ($joinCol !== false) {
  2702.                         $this->raiseError( 
  2703.                             "joinAdd: You cannot target a join column in the " .
  2704.                             "'link from' table ({$obj->__table}). " . 
  2705.                             "Either remove the fourth argument to joinAdd() ".
  2706.                             "({$joinCol}), or alter your links.ini file.",
  2707.                             DB_DATAOBJECT_ERROR_NODATA);
  2708.                         return false;
  2709.                     }
  2710.                 
  2711.                     $ofield = $k;
  2712.                     $tfield = $ar[1];
  2713.                     break;
  2714.                 }
  2715.             }
  2716.         }
  2717.  
  2718.         /* otherwise see if there are any links from this table to the obj. */
  2719.  
  2720.         if (($ofield === false) && ($links = $this->links())) {
  2721.             foreach ($links as $k => $v) {
  2722.                 /* link contains {this column} = {linked table}:{linked column} */
  2723.                 $ar = explode(':', $v);
  2724.                 if ($ar[0] == $obj->__table) {
  2725.                     if ($joinCol !== false) {
  2726.                         if ($k == $joinCol) {
  2727.                             $tfield = $k;
  2728.                             $ofield = $ar[1];
  2729.                             break;
  2730.                         } else {
  2731.                             continue;
  2732.                         }
  2733.                     } else {
  2734.                         $tfield = $k;
  2735.                         $ofield = $ar[1];
  2736.                         break;
  2737.                     }
  2738.                 }
  2739.             }
  2740.         }
  2741.         
  2742.         /* did I find a conneciton between them? */
  2743.  
  2744.         if ($ofield === false) {
  2745.             $this->raiseError(
  2746.                 "joinAdd: {$obj->__table} has no link with {$this->__table}",
  2747.                 DB_DATAOBJECT_ERROR_NODATA);
  2748.             return false;
  2749.         }
  2750.         $joinType = strtoupper($joinType);
  2751.         if ($joinAs === false) {
  2752.             $joinAs = $obj->__table;
  2753.         }
  2754.         
  2755.         $quoteIdentifiers = !empty($_DB_DATAOBJECT['CONFIG']['quote_identifiers']);
  2756.         
  2757.         $objTable = $quoteIdentifiers ? $DB->quoteIdentifier($obj->__table) : $obj->__table ;
  2758.         
  2759.         
  2760.         // nested (join of joined objects..)
  2761.         $appendJoin = '';
  2762.         if ($obj->_join) {
  2763.             // postgres allows nested queries, with ()'s
  2764.             // not sure what the results are with other databases..
  2765.             // may be unpredictable..
  2766.             if (in_array($DB->dsn["phptype"],array('pgsql'))) {
  2767.                 $objTable = "($objTable {$obj->_join})";
  2768.             } else {
  2769.                 $appendJoin = $obj->_join;
  2770.             }
  2771.         }
  2772.         
  2773.         
  2774.         $table = $this->__table;
  2775.         
  2776.         if ($quoteIdentifiers) {
  2777.             $joinAs   = $DB->quoteIdentifier($joinAs);
  2778.             $table    = $DB->quoteIdentifier($table);     
  2779.             $ofield   = $DB->quoteIdentifier($ofield);    
  2780.             $tfield   = $DB->quoteIdentifier($tfield);    
  2781.         }
  2782.         
  2783.         $fullJoinAs = '';
  2784.         if ($DB->quoteIdentifier($obj->__table) != $joinAs) {
  2785.             $fullJoinAs = "AS {$joinAs}";
  2786.         }
  2787.         
  2788.         switch ($joinType) {
  2789.             case 'INNER':
  2790.             case 'LEFT': 
  2791.             case 'RIGHT': // others??? .. cross, left outer, right outer, natural..?
  2792.                 $this->_join .= "\n {$joinType} JOIN {$objTable}  {$fullJoinAs}".
  2793.                                 " ON {$joinAs}.{$ofield}={$table}.{$tfield} {$appendJoin} ";
  2794.                 break;
  2795.             case '': // this is just a standard multitable select..
  2796.                 $this->_join .= "\n , {$objTable} {$fullJoinAs} {$appendJoin}";
  2797.                 $this->whereAdd("{$joinAs}.{$ofield}={$table}.{$tfield}");
  2798.         }
  2799.          
  2800.         // if obj only a dataobject - eg. no extended class has been defined..
  2801.         // it obvioulsy cant work out what child elements might exist...
  2802.         // untill we get on the fly querying of tables..
  2803.         if ( strtolower(get_class($obj)) == 'db_dataobject') {
  2804.             return true;
  2805.         }
  2806.          
  2807.         /* now add where conditions for anything that is set in the object */
  2808.     
  2809.     
  2810.     
  2811.         $items = $obj->table();
  2812.         // will return an array if no items..
  2813.         
  2814.         // only fail if we where expecting it to work (eg. not joined on a array)
  2815.         
  2816.         
  2817.         
  2818.         if (!$items) {
  2819.             $this->raiseError(
  2820.                 "joinAdd: No table definition for {$obj->__table}", 
  2821.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2822.             return false;
  2823.         }
  2824.  
  2825.         foreach($items as $k => $v) {
  2826.             if (!isset($obj->$k)) {
  2827.                 continue;
  2828.             }
  2829.             
  2830.             $kSql = ($quoteIdentifiers ? $DB->quoteIdentifier($k) : $k);
  2831.             
  2832.             
  2833.             if ($v & DB_DATAOBJECT_STR) {
  2834.                 $this->whereAdd("{$joinAs}.{$kSql} = " . $DB->quote($obj->$k));
  2835.                 continue;
  2836.             }
  2837.             if (is_numeric($obj->$k)) {
  2838.                 $this->whereAdd("{$joinAs}.{$kSql} = {$obj->$k}");
  2839.                 continue;
  2840.             }
  2841.             /* this is probably an error condition! */
  2842.             $this->whereAdd("{$joinAs}.{$kSql} = 0");
  2843.         }
  2844.         
  2845.         // and finally merge the whereAdd from the child..
  2846.         if (!$obj->_query['condition']) {
  2847.             return true;
  2848.         }
  2849.         $cond = preg_replace('/^\sWHERE/i','',$obj->_query['condition']);
  2850.         
  2851.         $this->whereAdd("($cond)");
  2852.         return true;
  2853.  
  2854.     }
  2855.  
  2856.     /**
  2857.      * Copies items that are in the table definitions from an
  2858.      * array or object into the current object
  2859.      * will not override key values.
  2860.      *
  2861.      *
  2862.      * @param    array | object  $from
  2863.      * @param    string  $format eg. map xxxx_name to $object->name using 'xxxx_%s' (defaults to %s - eg. name -> $object->name
  2864.      * @access   public
  2865.      * @return   true on success or array of key=>setValue error message
  2866.      */
  2867.     function setFrom(&$from, $format = '%s')
  2868.     {
  2869.         global $_DB_DATAOBJECT;
  2870.         $keys  = $this->keys();
  2871.         $items = $this->table();
  2872.         if (!$items) {
  2873.             $this->raiseError(
  2874.                 "setFrom:Could not find table definition for {$this->__table}", 
  2875.                 DB_DATAOBJECT_ERROR_INVALIDCONFIG);
  2876.             return;
  2877.         }
  2878.         $overload_return = array();
  2879.         foreach (array_keys($items) as $k) {
  2880.             if (in_array($k,$keys)) {
  2881.                 continue; // dont overwrite keys
  2882.             }
  2883.             if (!$k) {
  2884.                 continue; // ignore empty keys!!! what
  2885.             }
  2886.             if (is_object($from) && isset($from->{sprintf($format,$k)})) {
  2887.                 $kk = (strtolower($k) == 'from') ? '_from' : $k;
  2888.                 if (method_exists($this,'set'.$kk)) {
  2889.                     $ret = $this->{'set'.$kk}($from->{sprintf($format,$k)});
  2890.                     if (is_string($ret)) {
  2891.                         $overload_return[$k] = $ret;
  2892.                     }
  2893.                     continue;
  2894.                 }
  2895.                 $this->$k = $from->{sprintf($format,$k)};
  2896.                 continue;
  2897.             }
  2898.             
  2899.             if (is_object($from)) {
  2900.                 continue;
  2901.             }
  2902.             
  2903.             if (!isset($from[sprintf($format,$k)])) {
  2904.                 continue;
  2905.             }
  2906.             if (is_object($from[sprintf($format,$k)])) {
  2907.                 continue;
  2908.             }
  2909.             if (is_array($from[sprintf($format,$k)])) {
  2910.                 continue;
  2911.             }
  2912.             $kk = (strtolower($k) == 'from') ? '_from' : $k;
  2913.             if (method_exists($this,'set'. $kk)) {
  2914.                 $ret =  $this->{'set'.$kk}($from[sprintf($format,$k)]);
  2915.                 if (is_string($ret)) {
  2916.                     $overload_return[$k] = $ret;
  2917.                 }
  2918.                 continue;
  2919.             }
  2920.             $ret = $this->fromValue($k,$from[sprintf($format,$k)]);
  2921.             if ($ret !== true)  {
  2922.                 $overload_return[$k] = 'Not A Valid Value';
  2923.             }
  2924.             //$this->$k = $from[sprintf($format,$k)];
  2925.         }
  2926.         if ($overload_return) {
  2927.             return $overload_return;
  2928.         }
  2929.         return true;
  2930.     }
  2931.  
  2932.     /**
  2933.      * Returns an associative array from the current data
  2934.      * (kind of oblivates the idea behind DataObjects, but
  2935.      * is usefull if you use it with things like QuickForms.
  2936.      *
  2937.      * you can use the format to return things like user[key]
  2938.      * by sending it $object->toArray('user[%s]')
  2939.      *
  2940.      * will also return links converted to arrays.
  2941.      *
  2942.      * @param   string sprintf format for array
  2943.      * @access   public
  2944.      * @return   array of key => value for row
  2945.      */
  2946.  
  2947.     function toArray($format = '%s')
  2948.     {
  2949.         global $_DB_DATAOBJECT;
  2950.         $ret = array();
  2951.         $ar = isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]) ?
  2952.             array_merge($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid],$this->table()) :
  2953.             $this->table();
  2954.  
  2955.         foreach($ar as $k=>$v) {
  2956.              
  2957.             if (!isset($this->$k)) {
  2958.                 $ret[sprintf($format,$k)] = '';
  2959.                 continue;
  2960.             }
  2961.             // call the overloaded getXXXX() method.
  2962.             if (method_exists($this,'get'.$k)) {
  2963.                 $ret[sprintf($format,$k)] = $this->{'get'.$k}();
  2964.                 continue;
  2965.             }
  2966.             // should this call toValue() ???
  2967.             $ret[sprintf($format,$k)] = $this->$k;
  2968.         }
  2969.         if (!$this->_link_loaded) {
  2970.             return $ret;
  2971.         }
  2972.         foreach($this->_link_loaded as $k) {
  2973.             $ret[sprintf($format,$k)] = $this->$k->toArray();
  2974.         
  2975.         }
  2976.         
  2977.         return $ret;
  2978.     }
  2979.  
  2980.     /**
  2981.      * validate - override this to set up your validation rules
  2982.      *
  2983.      * validate the current objects values either just testing strings/numbers or
  2984.      * using the user defined validate{Row name}() methods.
  2985.      * will attempt to call $this->validate{column_name}() - expects true = ok  false = ERROR
  2986.      * you can the use the validate Class from your own methods.
  2987.      *
  2988.      * @access  public
  2989.      * @return  array of validation results or true
  2990.      */
  2991.     function validate()
  2992.     {
  2993.         require_once 'Validate.php';
  2994.         $table = $this->table();
  2995.         $ret   = array();
  2996.  
  2997.         foreach($table as $key => $val) {
  2998.             
  2999.             
  3000.             // call user defined validation always...
  3001.             $method = "Validate" . ucfirst($key);
  3002.             if (method_exists($this, $method)) {
  3003.                 $ret[$key] = $this->$method();
  3004.                 continue;
  3005.             }
  3006.             
  3007.             // if not null - and it's not set.......
  3008.             
  3009.             if (!isset($this->$key) && ($val & DB_DATAOBJECT_NOTNULL)) {
  3010.                 $ret[$key] = false;
  3011.                 continue;
  3012.             }
  3013.             
  3014.             if (is_string($this->$key) && (strtolower($this->$key) == 'null') && ($val & DB_DATAOBJECT_NOTNULL)) {
  3015.                 $ret[$key] = false;
  3016.                 continue;
  3017.             }
  3018.             // ignore things that are not set. ?
  3019.            
  3020.             if (!isset($this->$key)) {
  3021.                 continue;
  3022.             }
  3023.             
  3024.             // if the string is empty.. assume it is ok..
  3025.             if (!is_object($this->$key) && !is_array($this->$key) && !strlen((string) $this->$key)) {
  3026.                 continue;
  3027.             }
  3028.             
  3029.             switch (true) {
  3030.                 // todo: date time.....
  3031.                 
  3032.             
  3033.                 case  ($val & DB_DATAOBJECT_STR):
  3034.                     $ret[$key] = Validate::string($this->$key, VALIDATE_PUNCTUATION . VALIDATE_NAME);
  3035.                     continue;
  3036.                 case  ($val & DB_DATAOBJECT_INT):
  3037.                     $ret[$key] = Validate::number($this->$key, array('decimal'=>'.'));
  3038.                     continue;
  3039.             }
  3040.         }
  3041.  
  3042.         foreach ($ret as $key => $val) {
  3043.             if ($val === false) {
  3044.                 return $ret;
  3045.             }
  3046.         }
  3047.         return true; // everything is OK.
  3048.     }
  3049.  
  3050.     /**
  3051.      * Gets the DB object related to an object - so you can use funky peardb stuf with it :)
  3052.      *
  3053.      * @access public
  3054.      * @return object The DB connection
  3055.      */
  3056.     function &getDatabaseConnection()
  3057.     {
  3058.         global $_DB_DATAOBJECT;
  3059.  
  3060.         if (($e = $this->_connect()) !== true) {
  3061.             return $e;
  3062.         }
  3063.         if (!isset($_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5])) {
  3064.             return  false;
  3065.         }
  3066.         return $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5];
  3067.     }
  3068.  
  3069.  
  3070.     /**
  3071.      * Gets the DB result object related to the objects active query
  3072.      *  - so you can use funky pear stuff with it - like pager for example.. :)
  3073.      *
  3074.      * @access public
  3075.      * @return object The DB result object
  3076.      */
  3077.      
  3078.     function &getDatabaseResult()
  3079.     {
  3080.         global $_DB_DATAOBJECT;
  3081.         $this->_connect();
  3082.         if (!isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {
  3083.             return  false;
  3084.         }
  3085.         return $_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid];
  3086.     }
  3087.  
  3088.     /**
  3089.      * Overload Extension support
  3090.      *  - enables setCOLNAME/getCOLNAME
  3091.      *  if you define a set/get method for the item it will be called.
  3092.      * otherwise it will just return/set the value.
  3093.      * NOTE this currently means that a few Names are NO-NO's 
  3094.      * eg. links,link,linksarray, from, Databaseconnection,databaseresult
  3095.      *
  3096.      * note 
  3097.      *  - set is automatically called by setFrom.
  3098.      *   - get is automatically called by toArray()
  3099.      *  
  3100.      * setters return true on success. = strings on failure
  3101.      * getters return the value!
  3102.      *
  3103.      * this fires off trigger_error - if any problems.. pear_error, 
  3104.      * has problems with 4.3.2RC2 here
  3105.      *
  3106.      * @access public
  3107.      * @return true?
  3108.      * @see overload
  3109.      */
  3110.  
  3111.     
  3112.     function _call($method,$params,&$return) {
  3113.         
  3114.         //$this->debug("ATTEMPTING OVERLOAD? $method");
  3115.         // ignore constructors : - mm
  3116.         if (strtolower($method) == strtolower(get_class($this))) {
  3117.             return true;
  3118.         }
  3119.         $type = strtolower(substr($method,0,3));
  3120.         $class = get_class($this);
  3121.         if (($type != 'set') && ($type != 'get')) {
  3122.             return false;
  3123.         }
  3124.          
  3125.         
  3126.         
  3127.         // deal with naming conflick of setFrom = this is messy ATM!
  3128.         
  3129.         if (strtolower($method) == 'set_from') {
  3130.             $return = $this->toValue('from',isset($params[0]) ? $params[0] : null);
  3131.             return  true;
  3132.         }
  3133.         
  3134.         $element = substr($method,3);
  3135.         if ($element{0} == '_') {
  3136.             return false;
  3137.         }
  3138.          
  3139.         
  3140.         // dont you just love php's case insensitivity!!!!
  3141.         
  3142.         $array =  array_keys(get_class_vars($class));
  3143.         
  3144.         if (!in_array($element,$array)) {
  3145.             // munge case
  3146.             foreach($array as $k) {
  3147.                 $case[strtolower($k)] = $k;
  3148.             }
  3149.             if ((substr(phpversion(),0,1) == 5) && isset($case[strtolower($element)])) {
  3150.                 trigger_error("PHP5 set/get calls should match the case of the variable",E_USER_WARNING);
  3151.                 $element = strtolower($element);
  3152.             }
  3153.             
  3154.             // does it really exist?
  3155.             if (!isset($case[$element])) {
  3156.                 return false;            
  3157.             }
  3158.             // use the mundged case
  3159.             $element = $case[$element]; // real case !
  3160.         }
  3161.         
  3162.         
  3163.         if ($type == 'get') {
  3164.             $return = $this->toValue($element,isset($params[0]) ? $params[0] : null);
  3165.             return true;
  3166.         }
  3167.         
  3168.         
  3169.         $return = $this->fromValue($element, $params[0]);
  3170.          
  3171.         return true;
  3172.             
  3173.           
  3174.     }
  3175.         
  3176.     
  3177.     /**
  3178.     * standard set* implementation.
  3179.     *
  3180.     * takes data and uses it to set dates/strings etc.
  3181.     * normally called from __call..  
  3182.     *
  3183.     * Current supports
  3184.     *   date      = using (standard time format, or unixtimestamp).... so you could create a method :
  3185.     *               function setLastread($string) { $this->fromValue('lastread',strtotime($string)); }
  3186.     *
  3187.     *   time      = using strtotime 
  3188.     *   datetime  = using  same as date - accepts iso standard or unixtimestamp.
  3189.     *   string    = typecast only..
  3190.     * 
  3191.     * TODO: add formater:: eg. d/m/Y for date! ???
  3192.     *
  3193.     * @param   string       column of database
  3194.     * @param   mixed        value to assign
  3195.     *
  3196.     * @return   true| false     (False on error)
  3197.     * @access   public 
  3198.     * @see      DB_DataObject::_call
  3199.     */
  3200.   
  3201.     
  3202.     function fromValue($col,$value) 
  3203.     {
  3204.         $cols = $this->table();
  3205.         // dont know anything about this col..
  3206.         if (!isset($cols[$col])) {
  3207.             $this->$col = $value;
  3208.             return true;
  3209.         }
  3210.         //echo "FROM VALUE $col, {$cols[$col]}, $value\n";
  3211.         switch (true) {
  3212.             // set to null and column is can be null...
  3213.             case ((strtolower($value) == 'null') && (!($cols[$col] & DB_DATAOBJECT_NOTNULL))):
  3214.             case (is_object($value) && is_a($value,'DB_DataObject_Cast')): 
  3215.                 $this->$col = $value;
  3216.                 return true;
  3217.                 
  3218.             // fail on setting null on a not null field..
  3219.             case ((strtolower($value) == 'null') && ($cols[$col] & DB_DATAOBJECT_NOTNULL)):
  3220.                 return false;
  3221.         
  3222.             case (($cols[$col] & DB_DATAOBJECT_DATE) &&  ($cols[$col] & DB_DATAOBJECT_TIME)):
  3223.                 // empty values get set to '' (which is inserted/updated as NULl
  3224.                 if (!$value) {
  3225.                     $this->$col = '';
  3226.                 }
  3227.             
  3228.                 if (is_numeric($value)) {
  3229.                     $this->$col = date('Y-m-d H:i:s', $value);
  3230.                     return true;
  3231.                 }
  3232.               
  3233.                 // eak... - no way to validate date time otherwise...
  3234.                 $this->$col = (string) $value;
  3235.                 return true;
  3236.             
  3237.             case ($cols[$col] & DB_DATAOBJECT_DATE):
  3238.                 // empty values get set to '' (which is inserted/updated as NULl
  3239.                  
  3240.                 if (!$value) {
  3241.                     $this->$col = '';
  3242.                 }
  3243.             
  3244.                 if (is_numeric($value)) {
  3245.                     echo "it's numberic?";
  3246.                     $this->$col = date('Y-m-d',$value);
  3247.                     return true;
  3248.                 }
  3249.                  
  3250.                 // try date!!!!
  3251.                 require_once 'Date.php';
  3252.                 $x = new Date($value);
  3253.                 $this->$col = $x->format("%Y-%m-%d");
  3254.                 return true;
  3255.             
  3256.             case ($cols[$col] & DB_DATAOBJECT_TIME):
  3257.                 // empty values get set to '' (which is inserted/updated as NULl
  3258.                 if (!$value) {
  3259.                     $this->$col = '';
  3260.                 }
  3261.             
  3262.                 $guess = strtotime($value);
  3263.                 if ($guess != -1) {
  3264.                      $this->$col = date('H:i:s', $guess);
  3265.                     return $return = true;
  3266.                 }
  3267.                 // otherwise an error in type...
  3268.                 return false;
  3269.             
  3270.             case ($cols[$col] & DB_DATAOBJECT_STR):
  3271.                 
  3272.                 $this->$col = (string) $value;
  3273.                 return true;
  3274.                 
  3275.             // todo : floats numerics and ints...
  3276.             default:
  3277.                 $this->$col = $value;
  3278.                 return true;
  3279.         }
  3280.     
  3281.     
  3282.     
  3283.     }
  3284.      /**
  3285.     * standard get* implementation.
  3286.     *
  3287.     *  with formaters..
  3288.     * supported formaters:  
  3289.     *   date/time : %d/%m/%Y (eg. php strftime) or pear::Date 
  3290.     *   numbers   : %02d (eg. sprintf)
  3291.     *  NOTE you will get unexpected results with times like 0000-00-00 !!!
  3292.     *
  3293.     *
  3294.     * 
  3295.     * @param   string       column of database
  3296.     * @param   format       foramt
  3297.     *
  3298.     * @return   true     Description
  3299.     * @access   public 
  3300.     * @see      DB_DataObject::_call(),strftime(),Date::format()
  3301.     */
  3302.     function toValue($col,$format = null) 
  3303.     {
  3304.         if (is_null($format)) {
  3305.             return $this->$col;
  3306.         }
  3307.         $cols = $this->table();
  3308.         switch (true) {
  3309.             case (($cols[$col] & DB_DATAOBJECT_DATE) &&  ($cols[$col] & DB_DATAOBJECT_TIME)):
  3310.                 if (!$this->$col) {
  3311.                     return '';
  3312.                 }
  3313.                 $guess = strtotime($this->$col);
  3314.                 if ($guess != -1) {
  3315.                     return strftime($format, $guess);
  3316.                 }
  3317.                 // eak... - no way to validate date time otherwise...
  3318.                 return $this->$col;
  3319.             case ($cols[$col] & DB_DATAOBJECT_DATE):
  3320.                 if (!$this->$col) {
  3321.                     return '';
  3322.                 } 
  3323.                 $guess = strtotime($this->$col);
  3324.                 if ($guess != -1) {
  3325.                     return strftime($format,$guess);
  3326.                 }
  3327.                 // try date!!!!
  3328.                 require_once 'Date.php';
  3329.                 $x = new Date($this->$col);
  3330.                 return $x->format($format);
  3331.                 
  3332.             case ($cols[$col] & DB_DATAOBJECT_TIME):
  3333.                 if (!$this->$col) {
  3334.                     return '';
  3335.                 }
  3336.                 $guess = strtotime($this->$col);
  3337.                 if ($guess > -1) {
  3338.                     return strftime($format, $guess);
  3339.                 }
  3340.                 // otherwise an error in type...
  3341.                 return $this->$col;
  3342.                 
  3343.             case ($cols[$col] &  DB_DATAOBJECT_MYSQLTIMESTAMP):
  3344.                 if (!$this->$col) {
  3345.                     return '';
  3346.                 }
  3347.                 require_once 'Date.php';
  3348.                 
  3349.                 $x = new Date($this->$col);
  3350.                 
  3351.                 return $x->format($format);
  3352.             
  3353.             
  3354.             default:
  3355.                 return sprintf($format,$this->col);
  3356.         }
  3357.             
  3358.  
  3359.     }
  3360.     
  3361.     
  3362.     /* ----------------------- Debugger ------------------ */
  3363.  
  3364.     /**
  3365.      * Debugger. - use this in your extended classes to output debugging information.
  3366.      *
  3367.      * Uses DB_DataObject::DebugLevel(x) to turn it on
  3368.      *
  3369.      * @param    string $message - message to output
  3370.      * @param    string $logtype - bold at start
  3371.      * @param    string $level   - output level
  3372.      * @access   public
  3373.      * @return   none
  3374.      */
  3375.     function debug($message, $logtype = 0, $level = 1)
  3376.     {
  3377.         global $_DB_DATAOBJECT;
  3378.  
  3379.         if (empty($_DB_DATAOBJECT['CONFIG']['debug'])  || 
  3380.             (is_int($_DB_DATAOBJECT['CONFIG']['debug']) &&  $_DB_DATAOBJECT['CONFIG']['debug'] < $level)) {
  3381.             return;
  3382.         }
  3383.         // this is a bit flaky due to php's wonderfull class passing around crap..
  3384.         // but it's about as good as it gets..
  3385.         $class = (isset($this) && is_a($this,'DB_DataObject')) ? get_class($this) : 'DB_DataObject';
  3386.         
  3387.         if (!is_string($message)) {
  3388.             $message = print_r($message,true);
  3389.         }
  3390.         if (!is_int( $_DB_DATAOBJECT['CONFIG']['debug']) && is_callable( $_DB_DATAOBJECT['CONFIG']['debug'])) {
  3391.             return call_user_func($_DB_DATAOBJECT['CONFIG']['debug'], $class, $message, $logtype, $level);
  3392.         }
  3393.         
  3394.         if (!ini_get('html_errors')) {
  3395.             echo "$class   : $logtype       : $message\n";
  3396.             flush();
  3397.             return;
  3398.         }
  3399.         if (!is_string($message)) {
  3400.             $message = print_r($message,true);
  3401.         }
  3402.         echo "<code><B>$class: $logtype:</B> $message</code><BR>\n";
  3403.         flush();
  3404.     }
  3405.  
  3406.     /**
  3407.      * sets and returns debug level
  3408.      * eg. DB_DataObject::debugLevel(4);
  3409.      *
  3410.      * @param   int     $v  level
  3411.      * @access  public
  3412.      * @return  none
  3413.      */
  3414.     function debugLevel($v = null)
  3415.     {
  3416.         global $_DB_DATAOBJECT;
  3417.         if (empty($_DB_DATAOBJECT['CONFIG'])) {
  3418.             DB_DataObject::_loadConfig();
  3419.         }
  3420.         if ($v !== null) {
  3421.             $r = isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0;
  3422.             $_DB_DATAOBJECT['CONFIG']['debug']  = $v;
  3423.             return $r;
  3424.         }
  3425.         return isset($_DB_DATAOBJECT['CONFIG']['debug']) ? $_DB_DATAOBJECT['CONFIG']['debug'] : 0;
  3426.     }
  3427.  
  3428.     /**
  3429.      * Last Error that has occured
  3430.      * - use $this->_lastError or
  3431.      * $last_error = &PEAR::getStaticProperty('DB_DataObject','lastError');
  3432.      *
  3433.      * @access  public
  3434.      * @var     object PEAR_Error (or false)
  3435.      */
  3436.     var $_lastError = false;
  3437.  
  3438.     /**
  3439.      * Default error handling is to create a pear error, but never return it.
  3440.      * if you need to handle errors you should look at setting the PEAR_Error callback
  3441.      * this is due to the fact it would wreck havoc on the internal methods!
  3442.      *
  3443.      * @param  int $message    message
  3444.      * @param  int $type       type
  3445.      * @param  int $behaviour  behaviour (die or continue!);
  3446.      * @access public
  3447.      * @return error object
  3448.      */
  3449.     function raiseError($message, $type = null, $behaviour = null)
  3450.     {
  3451.         global $_DB_DATAOBJECT;
  3452.         
  3453.         if ($behaviour == PEAR_ERROR_DIE && !empty($_DB_DATAOBJECT['CONFIG']['dont_die'])) {
  3454.             $behaviour = null;
  3455.         }
  3456.         
  3457.         if (PEAR::isError($message)) {
  3458.             $error = $message;
  3459.         } else {
  3460.             require_once 'DB/DataObject/Error.php';
  3461.             $error = PEAR::raiseError($message, $type, $behaviour,
  3462.                             $opts=null, $userinfo=null, 'DB_DataObject_Error'
  3463.                         );
  3464.         }
  3465.         // this will never work totally with PHP's object model.
  3466.         // as this is passed on static calls (like staticGet in our case)
  3467.  
  3468.         if (@is_object($this) && is_subclass_of($this,'db_dataobject')) {
  3469.             $this->_lastError = $error;
  3470.         }
  3471.  
  3472.         $_DB_DATAOBJECT['LASTERROR'] = $error;
  3473.  
  3474.         // no checks for production here?.......
  3475.         DB_DataObject::debug($message,"ERROR",1);
  3476.         return $error;
  3477.     }
  3478.  
  3479.     /**
  3480.      * Define the global $_DB_DATAOBJECT['CONFIG'] as an alias to  PEAR::getStaticProperty('DB_DataObject','options');
  3481.      *
  3482.      * After Profiling DB_DataObject, I discoved that the debug calls where taking
  3483.      * considerable time (well 0.1 ms), so this should stop those calls happening. as
  3484.      * all calls to debug are wrapped with direct variable queries rather than actually calling the funciton
  3485.      * THIS STILL NEEDS FURTHER INVESTIGATION
  3486.      *
  3487.      * @access   public
  3488.      * @return   object an error object
  3489.      */
  3490.     function _loadConfig()
  3491.     {
  3492.         global $_DB_DATAOBJECT;
  3493.  
  3494.         $_DB_DATAOBJECT['CONFIG'] = &PEAR::getStaticProperty('DB_DataObject','options');
  3495.  
  3496.  
  3497.     }
  3498.      /**
  3499.      * Free global arrays associated with this object.
  3500.      *
  3501.      * Note: as we now store resultfields in a global, it is never freed, if you do alot of calls to find(), 
  3502.      * memory will grow gradually.
  3503.      * 
  3504.      *
  3505.      * @access   public
  3506.      * @return   none
  3507.      */
  3508.     function free() 
  3509.     {
  3510.         global $_DB_DATAOBJECT;
  3511.           
  3512.         if (isset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid])) {
  3513.             unset($_DB_DATAOBJECT['RESULTFIELDS'][$this->_DB_resultid]);
  3514.         }
  3515.         if (isset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid])) {     
  3516.             unset($_DB_DATAOBJECT['RESULTS'][$this->_DB_resultid]);
  3517.         }
  3518.         // this is a huge bug in DB!
  3519.         $_DB_DATAOBJECT['CONNECTIONS'][$this->_database_dsn_md5]->num_rows = array();
  3520.         
  3521.     }
  3522.      
  3523.     
  3524.     /* ---- LEGACY BC METHODS - NOT DOCUMENTED - See Documentation on New Methods. ---*/
  3525.     
  3526.     function _get_table() { return $this->table(); }
  3527.     function _get_keys()  { return $this->keys();  }
  3528.     
  3529.     
  3530.     
  3531.     
  3532. }
  3533. // technially 4.3.2RC1 was broken!!
  3534. // looks like 4.3.3 may have problems too....
  3535. if (!defined('DB_DATAOBJECT_NO_OVERLOAD')) {
  3536.  
  3537.     if ((phpversion() != '4.3.2-RC1') && (version_compare( phpversion(), "4.3.1") > 0)) {
  3538.         if (version_compare( phpversion(), "5") < 0) {
  3539.            overload('DB_DataObject');
  3540.         } 
  3541.         $GLOBALS['_DB_DATAOBJECT']['OVERLOADED'] = true;
  3542.     }
  3543. }
  3544.